Compare commits

..

1 Commits

Author SHA1 Message Date
Terry Kaul
6cd942bbcc MSA-2073: Multifunction pool control 2017-07-03 06:17:12 -07:00
14 changed files with 502 additions and 531 deletions

View File

@@ -0,0 +1,464 @@
/**
* Intermatic PE653 Pool Control System
*
* Copyright 2014 bigpunk6
*
* 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.
*
* Don't use Cooper Lee's code (vTile_ms, ms_w_vts) he was working on a different implementation than me.
*
* Install my device type then use the Multi-Channel Controll App by SmartThings from the Marketplace under the More section.
*
*/
metadata {
definition (name: "Intermatic PE653 Pool Control System", author: "bigpunk6", namespace: "bigpunk6") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Temperature Measurement"
capability "Sensor"
capability "Zw Multichannel"
capability "Thermostat"
attribute "operationMode", "string"
attribute "firemanTimeout", "string"
attribute "temperatureOffsets", "string"
attribute "poolspaConfig", "string"
attribute "poolSetpoint", "string"
attribute "spaSetpoint", "string"
attribute "setPoolSetpoint", "string"
attribute "pool", "string"
attribute "setSpaSetpoint", "string"
attribute "spa", "string"
command "quickSetPool"
command "quickSetSpa"
fingerprint deviceId: "0x1001", inClusters: "0x91,0x73,0x72,0x86,0x81,0x60,0x70,0x85,0x25,0x27,0x43,0x31", outClusters: "0x82"
}
preferences {
input "operationMode1", "enum", title: "Boster/Cleaner Pump",
options:[1:"No",
2:"Uses Circuit-1",
3:"Variable Speed pump Speed-1",
4:"Variable Speed pump Speed-2",
5:"Variable Speed pump Speed-3",
6:"Variable Speed pump Speed-4"]
input "operationMode2", "enum", title: "Pump Type",
options:[0:"1 Speed Pump without Booster/Cleaner",
1:"1 Speed Pump with Booster/Cleaner",
2:"2 Speed Pump without Booster/Cleaner",
3:"2 Speed Pump with Booster/Cleaner"]
input "poolSpa1", "enum", title: "Pool or Spa", options:[0:"Pool",1:"Spa",2:"Both"]
input "fireman", "enum", title: "Fireman Timeout",
options:["255":"No heater installed",
"0":"No cool down period",
"1":"1 minute",
"2":"2 minute",
"3":"3 minute",
"4":"4 minute",
"5":"5 minute",
"6":"6 minute",
"7":"7 minute",
"8":"8 minute",
"9":"9 minute",
"10":"10 minute",
"11":"11 minute",
"12":"12 minute",
"13":"13 minute",
"14":"14 minute",
"15":"15 minute"]
input "tempOffsetwater", "number", title: "Water temperature offset", defaultValue: 0, required: true
input "tempOffsetair", "number",
title: "Air temperature offset - Sets the Offset of the air temerature for the add-on Thermometer in degrees Fahrenheit -20F to +20F", defaultValue: 0, required: true
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
reply "8E010101,delay 800,6007": "command: 6008, payload: 4004"
reply "8505": "command: 8506, payload: 02"
reply "59034002": "command: 5904, payload: 8102003101000000"
reply "6007": "command: 6008, payload: 0002"
reply "600901": "command: 600A, payload: 10002532"
reply "600902": "command: 600A, payload: 210031"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"temperature", type: "thermostat", width: 6, height: 4){
tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
attributeState "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 32, color: "#153591"],
[value: 54, color: "#1e9cbb"],
[value: 64, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 90, color: "#f1d801"],
[value: 98, color: "#d04e00"],
[value: 110, color: "#bc2323"]
]
}
tileAttribute ("device.poolSetpoint", key: "VALUE_CONTROL") {
attributeState "poolSetpoint", action:"quickSetPool"
}
tileAttribute ("device.spaSetpoint", key: "SECONDARY_CONTROL") {
attributeState "spaSetpoint", label:'Spa set to ${currentValue}°F'
}
}
controlTile("poolSliderControl", "device.poolSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(40..104)") {
state "setPoolSetpoint", action:"quickSetPool", backgroundColor:"#d04e00"
}
valueTile("poolSetpoint", "device.poolSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "pool", label:'${currentValue}° pool', backgroundColor:"#ffffff"
}
controlTile("spaSliderControl", "device.spaSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(40..104)") {
state "setSpaSetpoint", action:"quickSetSpa", backgroundColor: "#1e9cbb"
}
valueTile("spaSetpoint", "device.spaSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "spa", label:'${currentValue}° spa', backgroundColor:"#ffffff"
}
/*valueTile("temperature", "device.temperature") {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 32, color: "#153591"],
[value: 54, color: "#1e9cbb"],
[value: 64, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 90, color: "#f1d801"],
[value: 98, color: "#d04e00"],
[value: 110, color: "#bc2323"]
]
)
}*/
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main "temperature"
details(["temperature", "poolSliderControl", "poolSetpoint", "spaSliderControl", "spaSetpoint", "configure", "refresh"])
}
}
def parse(String description) {
def result = null
if (description.startsWith("Err")) {
log.warn "Error in Parse"
result = createEvent(descriptionText:description, isStateChange:true)
} else {
def cmd = zwave.parse(description, [0x20: 1, 0x25:1, 0x27:1, 0x31:1, 0x43:1, 0x60:3, 0x70:2, 0x81:1, 0x85:1, 0x86: 1, 0x73:1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
log.debug("'$description' parsed to $result")
return result
}
//Thermostat
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
log.debug "ThermostatModeReport $cmd"
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "pool"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_FURNACE:
map.value = "spa"
break
}
map.name = "thermostatMode"
map
}
def quickSetPool(degrees) {
log.debug "quickSetPool $degrees"
setPoolSetpoint(degrees, 1000)
}
def setPoolSetpoint(degrees, delay = 30000) {
setPoolSetpoint(degrees.toDouble(), delay)
}
def setPoolSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setPoolSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()
], delay)
}
def quickSetSpa(degrees) {
setSpaSetpoint(degrees, 1000)
}
def setSpaSetpoint(degrees, delay = 30000) {
setSpaSetpoint(degrees.toDouble(), delay)
}
def setSpaSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setSpaSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 7, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format()
], delay)
}
//Reports
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
log.debug "configuration: $cmd"
def map = [:]
map.value = cmd.configurationValue
map.displayed = false
switch (cmd.parameterNumber) {
case 1:
map.name = "operationMode"
break;
case 2:
map.name = "firemanTimeout"
break;
case 3:
map.name = "temperatureOffsets"
break;
case 19:
map.name = "poolspaConfig"
break;
default:
return [:]
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd) {
log.debug "Sensor: $cmd"
def map = [:]
map.value = cmd.scaledSensorValue.toString()
map.unit = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv1.ThermostatSetpointReport cmd) {
def map = [:]
map.value = cmd.scaledValue.toString()
map.unit = cmd.scale == 1 ? "F" : "C"
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "poolSetpoint"
break;
case 7:
map.name = "spaSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "$cmd"
if (cmd.value == 0) {
createEvent(name: "switch", value: "off")
} else if (cmd.value == 255) {
createEvent(name: "switch", value: "on")
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceReport cmd) {
log.debug "MultiInstanceReport $cmd"
}
private List loadEndpointInfo() {
if (state.endpointInfo) {
state.endpointInfo
} else if (device.currentValue("epInfo")) {
fromJson(device.currentValue("epInfo"))
} else {
[]
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd) {
log.debug "$cmd"
updateDataValue("endpoints", cmd.endPoints.toString())
if (!state.endpointInfo) {
state.endpointInfo = loadEndpointInfo()
}
if (state.endpointInfo.size() > cmd.endPoints) {
cmd.endpointInfo
}
state.endpointInfo = [null] * cmd.endPoints
//response(zwave.associationV2.associationGroupingsGet())
[ createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:""),
response(zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: 1)) ]
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) {
log.debug "$cmd"
def result = []
def cmds = []
if(!state.endpointInfo) state.endpointInfo = []
state.endpointInfo[cmd.endPoint - 1] = cmd.format()[6..-1]
if (cmd.endPoint < getDataValue("endpoints").toInteger()) {
cmds = zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: cmd.endPoint + 1).format()
} else {
log.debug "endpointInfo: ${state.endpointInfo.inspect()}"
}
result << createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:"")
if(cmds) result << response(cmds)
result
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationGroupingsReport cmd) {
log.debug "$cmd"
state.groups = cmd.supportedGroupings
if (cmd.supportedGroupings > 1) {
[response(zwave.associationGrpInfoV1.associationGroupInfoGet(groupingIdentifier:2, listMode:1))]
}
}
def zwaveEvent(physicalgraph.zwave.commands.associationgrpinfov1.AssociationGroupInfoReport cmd) {
log.debug "$cmd"
def cmds = []
for (def i = 2; i <= state.groups; i++) {
cmds << response(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:i, nodeId:zwaveHubNodeId))
}
cmds
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
log.debug "$cmd"
def encapsulatedCommand = cmd.encapsulatedCommand([0x32: 3, 0x25: 1, 0x20: 1])
if (encapsulatedCommand) {
if (state.enabledEndpoints.find { it == cmd.sourceEndPoint }) {
def formatCmd = ([cmd.commandClass, cmd.command] + cmd.parameter).collect{ String.format("%02X", it) }.join()
createEvent(name: "epEvent", value: "$cmd.sourceEndPoint:$formatCmd", isStateChange: true, displayed: false, descriptionText: "(fwd to ep $cmd.sourceEndPoint)")
} else {
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Captured zwave command $cmd"
createEvent(descriptionText: "$device.displayName: $cmd", isStateChange: true)
}
//Commands
def epCmd(Integer ep, String cmds) {
log.debug "epCmd: $ep $cmds"
if (cmds.contains('2001FF')){
log.debug "contained 2001FF"
delayBetween([
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF), ep),
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
], 2300)
} else if (cmds.contains('200100')) {
log.debug "contained 2001FF"
delayBetween([
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0), ep),
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
], 2300)
} else if (cmds.contains('2002')) {
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
} else {
log.warn "No CMD found"
}
}
def enableEpEvents(enabledEndpoints) {
state.enabledEndpoints = enabledEndpoints.split(",").findAll()*.toInteger()
null
}
private command(physicalgraph.zwave.Command cmd) {
log.debug "command: $cmd"
cmd.format()
}
private encap(cmd, endpoint) {
if (endpoint) {
command(zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint, sourceEndPoint: endpoint).encapsulate(cmd))
} else {
command(cmd)
}
}
def poll() {
zwave.sensorMultilevelV1.sensorMultilevelGet().format()
}
def refresh() {
delayBetween([
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:2, destinationEndPoint:2, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:3, destinationEndPoint:3, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:4, destinationEndPoint:4, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:5, destinationEndPoint:5, commandClass:37, command:2).format(),
zwave.sensorMultilevelV1.sensorMultilevelGet().format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format(),
zwave.configurationV2.configurationGet(parameterNumber: 1).format(),
zwave.configurationV2.configurationGet(parameterNumber: 2).format(),
zwave.configurationV2.configurationGet(parameterNumber: 3).format(),
zwave.configurationV2.configurationGet(parameterNumber: 19).format()
], 3000)
}
def configure() {
def cmds = []
cmds << zwave.configurationV2.configurationSet(configurationValue: [operationMode1.toInteger(), operationMode2.toInteger()], parameterNumber: 1, size: 2).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [tempOffsetwater.toInteger(), tempOffsetair.toInteger(), 0, 0], parameterNumber: 3, size: 4).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [poolSpa1.toInteger()], parameterNumber: 19, size: 1).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [fireman.toInteger()], parameterNumber: 2, size: 1).format()
delayBetween(cmds, 2500)
}

View File

@@ -1,5 +1,5 @@
/**
* Spruce Sensor -updated with SLP model number 5/2017
* Spruce Sensor -Pre-release V2 10/8/2015
*
* Copyright 2014 Plaid Systems
*
@@ -14,33 +14,25 @@
*
-------10/20/2015 Updates--------
-Fix/add battery reporting interval to update
-remove polling and/or refresh
-------5/2017 Updates--------
-Add fingerprints for SLP
-add device health, check every 60mins + 2mins
-remove polling and/or refresh(?)
*/
metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "Plaid Systems") {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
capability "Configuration"
capability "Battery"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"
capability "Sensor"
capability "Health Check"
//capability "Polling"
attribute "maxHum", "string"
attribute "minHum", "string"
command "resetHumidity"
command "refresh"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01", deviceJoinName: "Spruce Sensor"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-SLP1", deviceJoinName: "Spruce Sensor"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
}
preferences {
@@ -301,11 +293,6 @@ def setConfig(){
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
}
def installed(){
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
//when device preferences are changed
def updated(){
log.debug "device updated"
@@ -316,8 +303,6 @@ def updated(){
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
}
}
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
//poll
@@ -410,4 +395,4 @@ private byte[] reverseArray(byte[] array) {
i++;
}
return array
}
}

View File

@@ -1,284 +0,0 @@
/**
* SmartThings Device Handler: Yamaha Zone
*
* Author: redloro@gmail.com
*
* 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.
*/
metadata {
definition (name: "Yamaha Zone", namespace: "redloro-smartthings", author: "redloro@gmail.com") {
/**
* List our capabilties. Doing so adds predefined command(s) which
* belong to the capability.
*/
capability "Music Player"
capability "Switch"
capability "Switch Level"
capability "Refresh"
capability "Polling"
capability "Sensor"
capability "Actuator"
/**
* Define all commands, ie, if you have a custom action not
* covered by a capability, you NEED to define it here or
* the call will not be made.
*
* To call a capability function, just prefix it with the name
* of the capability, for example, refresh would be "refresh.refresh"
*/
command "source0"
command "source1"
command "source2"
command "source3"
command "source4"
command "source5"
command "mutedOn"
command "mutedOff"
command "partyModeOn"
command "partyModeOff"
command "zone"
}
/**
* Define the various tiles and the states that they can be in.
* The 2nd parameter defines an event which the tile listens to,
* if received, it tries to map it to a state.
*
* You can also use ${currentValue} for the value of the event
* or ${name} for the name of the event. Just make SURE to use
* single quotes, otherwise it will only be interpreted at time of
* launch, instead of every time the event triggers.
*/
tiles(scale: 2) {
multiAttributeTile(name:"state", type:"lighting", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'On', action:"switch.off", icon:"st.Electronics.electronics16", backgroundColor:"#79b821", nextState:"off"
attributeState "off", label:'Off', action:"switch.on", icon:"st.Electronics.electronics16", backgroundColor:"#ffffff", nextState:"on"
}
tileAttribute ("source", key: "SECONDARY_CONTROL") {
attributeState "source", label:'${currentValue}'
}
}
// row
//controlTile("volume", "device.volume", "slider", height: 1, width: 6, range:"(0..100)") {
controlTile("volume", "device.volume", "slider", height: 1, width: 6, range:"(-80..16)") {
state "volume", label: "Volume", action:"music Player.setLevel", backgroundColor:"#00a0dc"
}
// row
standardTile("0", "device.source0", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV1", action:"source0", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV1", action:"source0", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
standardTile("1", "device.source1", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV2", action:"source1", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV2", action:"source1", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
standardTile("2", "device.source2", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV3", action:"source2", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV3", action:"source2", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
standardTile("3", "device.source3", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV4", action:"source3", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV4", action:"source3", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
standardTile("4", "device.source4", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV5", action:"source4", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV5", action:"source4", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
standardTile("5", "device.source5", decoration: "flat", width: 2, height: 2) {
state("off", label:"AV6", action:"source5", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff")
state("on", label:"AV6", action:"source5", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-green.png", backgroundColor:"#ffffff")
}
// row
standardTile("muted", "device.muted", decoration: "flat", width: 2, height: 2) {
state("off", label:'Muted', action:"mutedOn", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff", nextState:"on")
state("on", label:'Muted', action:"mutedOff", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-mute.png", backgroundColor:"#ffffff", nextState:"off")
}
standardTile("partyMode", "device.partyMode", decoration: "flat", width: 2, height: 2, inactiveLabel: false) {
state("off", label:'Party Mode', action:"partyModeOn", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-gray.png", backgroundColor:"#ffffff", nextState:"on")
state("on", label:'Party Mode', action:"partyModeOff", icon:"https://raw.githubusercontent.com/redloro/smartthings/master/images/indicator-dot-party.png", backgroundColor:"#ffffff", nextState:"off")
}
standardTile("refresh", "device.status", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Refresh", action:"refresh.refresh", icon:"st.secondary.refresh-icon", backgroundColor:"#ffffff"
}
// Defines which tile to show in the overview
main "state"
// Defines which tile(s) to show when user opens the detailed view
details([
"state",
"volume",
"0","1","2","3","4","5",
"muted", "partyMode","refresh"
])
}
preferences {
input name: "source0", type: "text", title: "Source 1", defaultValue: "AV1"
input name: "source1", type: "text", title: "Source 2", defaultValue: "AV2"
input name: "source2", type: "text", title: "Source 3", defaultValue: "AV3"
input name: "source3", type: "text", title: "Source 4", defaultValue: "AV4"
input name: "source4", type: "text", title: "Source 5", defaultValue: "AV5"
input name: "source5", type: "text", title: "Source 6", defaultValue: "AV6"
}
}
/**************************************************************************
* The following section simply maps the actions as defined in
* the metadata into onAction() calls.
*
* This is preferred since some actions can be dealt with more
* efficiently this way. Also keeps all user interaction code in
* one place.
*
*/
def on() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Power_Control><Power>On</Power></Power_Control></${getZone()}></YAMAHA_AV>")
sendEvent(name: "switch", value: "on")
}
def off() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Power_Control><Power>Standby</Power></Power_Control></${getZone()}></YAMAHA_AV>")
sendEvent(name: "switch", value: "off")
}
def setLevel(value) {
//sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Volume><Lvl><Val>${(Math.round(value * 9 / 5) * 5 - 800).intValue()}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></${getZone()}></YAMAHA_AV>")
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Volume><Lvl><Val>${(value * 10).intValue()}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></${getZone()}></YAMAHA_AV>")
sendEvent(name: "volume", value: value)
}
def source0() {
setSource(0)
}
def source1() {
setSource(1)
}
def source2() {
setSource(2)
}
def source3() {
setSource(3)
}
def source4() {
setSource(4)
}
def source5() {
setSource(5)
}
def mutedOn() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Volume><Mute>On</Mute></Volume></${getZone()}></YAMAHA_AV>")
sendEvent(name: "muted", value: "on")
}
def mutedOff() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Volume><Mute>Off</Mute></Volume></${getZone()}></YAMAHA_AV>")
sendEvent(name: "muted", value: "off")
}
def partyModeOn() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><System><Party_Mode><Mode>On</Mode></Party_Mode></System></YAMAHA_AV>")
sendEvent(name: "partyMode", value: "on")
}
def partyModeOff() {
sendCommand("<YAMAHA_AV cmd=\"PUT\"><System><Party_Mode><Mode>Off</Mode></Party_Mode></System></YAMAHA_AV>")
sendEvent(name: "partyMode", value: "off")
}
def refresh() {
sendCommand("<YAMAHA_AV cmd=\"GET\"><${getZone()}><Basic_Status>GetParam</Basic_Status></${getZone()}></YAMAHA_AV>")
sendCommand("<YAMAHA_AV cmd=\"GET\"><System><Party_Mode><Mode>GetParam</Mode></Party_Mode></System></YAMAHA_AV>")
}
/**************************************************************************/
/**
* Called every so often (every 5 minutes actually) to refresh the
* tiles so the user gets the correct information.
*/
def poll() {
refresh()
}
def parse(String description) {
return
}
def setSource(id) {
//log.debug "source: "+getSourceName(id)
sendCommand("<YAMAHA_AV cmd=\"PUT\"><${getZone()}><Input><Input_Sel>"+getSourceName(id)+"</Input_Sel></Input></${getZone()}></YAMAHA_AV>")
setSourceTile(getSourceName(id))
}
def getSourceName(id) {
if (settings) {
return settings."source${id}"
} else {
return ['AV1', 'AV2', 'AV3', 'AV4', 'AV5', 'AV6'].get(id)
}
}
def setSourceTile(name) {
sendEvent(name: "source", value: "Source: ${name}")
for (def i = 0; i < 6; i++) {
if (name == getSourceName(i)) {
sendEvent(name: "source${i}", value: "on")
}
else {
sendEvent(name: "source${i}", value: "off")
}
}
}
def zone(evt) {
/*
* Zone On/Off
*/
if (evt.Basic_Status.Power_Control.Power.text()) {
sendEvent(name: "switch", value: (evt.Basic_Status.Power_Control.Power.text() == "On") ? "on" : "off")
}
/*
* Zone Volume
*/
if (evt.Basic_Status.Volume.Lvl.Val.text()) {
def int volLevel = evt.Basic_Status.Volume.Lvl.Val.toInteger() ?: -250
//sendEvent(name: "volume", value: ((volLevel + 800) / 9).intValue())
sendEvent(name: "volume", value: (volLevel / 10).intValue())
}
/*
* Zone Muted
*/
if (evt.Basic_Status.Volume.Mute.text()) {
sendEvent(name: "muted", value: (evt.Basic_Status.Volume.Mute.text() == "On") ? "on" : "off")
}
/*
* Zone Source
*/
if (evt.Basic_Status.Input.Input_Sel.text()) {
setSourceTile(evt.Basic_Status.Input.Input_Sel.text())
}
/*
* Party Mode
*/
if (evt.Party_Mode.Mode.text()) {
sendEvent(name: "partyMode", value: (evt.Party_Mode.Mode.text() == "On") ? "on" : "off")
}
}
private sendCommand(body) {
parent.sendCommand(body)
}
private getZone() {
return new String(device.deviceNetworkId).tokenize('|')[2]
}

View File

@@ -158,7 +158,7 @@ def generateEvent(Map results) {
if(results) {
def linkText = getLinkText(device)
def supportedThermostatModes = ["off"]
def supportedThermostatModes = []
def thermostatMode = null
results.each { name, value ->

View File

@@ -105,8 +105,6 @@ def parse(String description) {
} else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
}
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
}
}
} else if (map.name == "temperature") {
@@ -131,10 +129,6 @@ def parse(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
}
@@ -203,8 +197,7 @@ def ping() {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
return refreshCmds + zigbee.enrollResponse()
}

View File

@@ -111,8 +111,6 @@ def parse(String description) {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
log.debug "Doing a read attr motion event"
map = getMotionResult(value)
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
}
}
} else if (map.name == "temperature") {
@@ -137,10 +135,6 @@ def parse(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
}
@@ -206,8 +200,7 @@ def refresh() {
log.debug "refresh called"
def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000)
return refreshCmds + zigbee.enrollResponse()
}

View File

@@ -134,9 +134,8 @@ def parse(String description) {
} else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
}
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
maps += translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} else {
maps += handleAcceleration(descMap)
}
}
@@ -230,11 +229,6 @@ private List<Map> parseAxis(List<Map> attrData) {
private List<Map> parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private List<Map> translateZoneStatus(ZoneStatus zs) {
List<Map> results = []
if (garageSensor != "Yes") {
@@ -319,7 +313,7 @@ def refresh() {
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS) + zigbee.enrollResponse()
zigbee.enrollResponse()
return refreshCmds
}

View File

@@ -28,8 +28,9 @@ Works with:
## Device Health
ZigBee Button is marked offline only in the case when Hub is offline.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __722min__ checkInterval
## Troubleshooting

View File

@@ -13,8 +13,6 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType
metadata {
@@ -185,6 +183,13 @@ private Map parseNonIasButtonMessage(Map descMap){
}
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
log.debug "Refreshing Battery"
@@ -193,6 +198,8 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device (plus 2 mins lag time)
sendEvent(name: "checkInterval", value: 2 * 6 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def cmds = []
if (device.getDataValue("model") == "3450-L") {
@@ -252,8 +259,6 @@ def updated() {
}
def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2)
}

View File

@@ -58,7 +58,6 @@ metadata {
def installed() {
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
response(refresh())
}
def updated() {

View File

@@ -1,154 +0,0 @@
/**
* SmartThings SmartApp: Yamaha Network Receiver
*
* Author: redloro@gmail.com
*
* 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.
*
* https://github.com/PSeitz/yamaha-nodejs
* http://<RECEIVER_IP_ADDRESS>/YamahaRemoteControl/desc.xml
*/
import groovy.util.XmlSlurper
definition(
name: "Yamaha Receiver",
namespace: "redloro-smartthings",
author: "redloro@gmail.com",
description: "Yamaha SmartApp",
category: "My Apps",
iconUrl: "https://raw.githubusercontent.com/redloro/smartthings/master/images/yamaha-receiver.png",
iconX2Url: "https://raw.githubusercontent.com/redloro/smartthings/master/images/yamaha-receiver.png",
iconX3Url: "https://raw.githubusercontent.com/redloro/smartthings/master/images/yamaha-receiver.png"
)
preferences {
section("SmartThings Hub") {
input "hostHub", "hub", title: "Select Hub", multiple: false, required: true
}
section("Yamaha Receiver") {
input name: "receiverName", type: "text", title: "Name", required: true, defaultValue: "Yamaha"
input name: "receiverIp", type: "text", title: "IP", required: true
input name: "receiverZones", type: "enum", title: "Zones", required: true, multiple: true, options: ["Main_Zone","Zone_B","Zone_2","Zone_3","Zone_4"]
}
}
def installed() {
subscribeToEvents()
}
def subscribeToEvents() {
subscribe(location, null, lanResponseHandler, [filterEvents:false])
}
def updated() {
addChildDevices()
}
def uninstalled() {
removeChildDevices()
}
def lanResponseHandler(evt) {
def map = stringToMap(evt.stringValue)
//verify that this message is from Yamaha Receiver IP
if (!map.ip || map.ip != convertIPtoHex(settings.receiverIp)) {
return
}
def headers = getHttpHeaders(map.headers);
def body = getHttpBody(map.body);
//log.trace "Headers: ${headers}"
//log.trace "Body: ${body}"
updateZoneDevices(body.children()[0])
}
private updateZoneDevices(evt) {
//log.debug "updateZoneDevices: ${evt.toString()}"
if (evt.name() == "System") {
//log.debug "Update all zones"
childDevices*.zone(evt)
return
}
def zonedevice = getChildDevice(getDeviceId(evt.name()))
if (zonedevice) {
zonedevice.zone(evt)
}
//check for Zone_B
zonedevice = getChildDevice(getDeviceId("Zone_B"))
if (zonedevice && evt.name() == "Main_Zone") {
zonedevice.zone(evt)
}
}
private addChildDevices() {
// add yamaha device
settings.receiverZones.each {
def deviceId = getDeviceId(it)
if (!getChildDevice(deviceId)) {
addChildDevice("redloro-smartthings", "Yamaha Zone", deviceId, hostHub.id, ["name": it, label: "${settings.receiverName}: ${it}", completedSetup: true])
log.debug "Added Yamaha zone: ${deviceId}"
}
}
childDevices*.refresh()
}
private removeChildDevices() {
getAllChildDevices().each { deleteChildDevice(it.deviceNetworkId) }
}
private sendCommand(body) {
//log.debug "Yamaha Network Receiver send command: ${body}"
def hubAction = new physicalgraph.device.HubAction(
headers: [HOST: getReceiverAddress()],
method: "POST",
path: "/YamahaRemoteControl/ctrl",
body: body
)
sendHubCommand(hubAction)
}
private getHttpHeaders(headers) {
def obj = [:]
new String(headers.decodeBase64()).split("\r\n").each {param ->
def nameAndValue = param.split(":")
obj[nameAndValue[0]] = (nameAndValue.length == 1) ? "" : nameAndValue[1].trim()
}
return obj
}
private getHttpBody(body) {
def obj = null;
if (body) {
obj = new XmlSlurper().parseText(new String(body.decodeBase64()))
}
return obj
}
private getDeviceId(zone) {
return "yamaha|${settings.receiverIp}|${zone}"
}
private getReceiverAddress() {
return settings.receiverIp + ":80"
}
private String convertIPtoHex(ipAddress) {
return ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join().toUpperCase()
}
private String convertPortToHex(port) {
return port.toString().format( '%04x', port.toInteger() ).toUpperCase()
}

View File

@@ -27,9 +27,10 @@ definition(
preferences {
page(name: "selectButton")
for (def i=1; i<=8; i++) {
page(name: "configureButton$i")
}
page(name: "configureButton1")
page(name: "configureButton2")
page(name: "configureButton3")
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -59,45 +60,22 @@ def selectButton() {
}
}
def createPage(pageNum) {
if ((state.numButton == pageNum) || (pageNum == 8))
state.installCondition = true
dynamicPage(name: "configureButton$pageNum", title: "Set up button $pageNum here",
nextPage: "configureButton${pageNum+1}", install: state.installCondition, uninstall: configured(), getButtonSections(pageNum))
}
def configureButton1() {
state.numButton = buttonDevice.currentState("numberOfButtons")?.longValue ?: 4
log.debug "state variable numButton: ${state.numButton}"
state.installCondition = false
createPage(1)
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
}
def configureButton2() {
createPage(2)
dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
}
def configureButton3() {
createPage(3)
dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
}
def configureButton4() {
createPage(4)
}
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
install: true, uninstall: true, getButtonSections(4))
}
def getButtonSections(buttonNumber) {

View File

@@ -202,8 +202,7 @@ def inputSelectionPage() {
section("options variations") {
paragraph "tap these elements and look at the differences when selecting an option"
input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", options: ["Thing 1", "Thing 2", "(Complicated) Thing 3"])
input(type: "enum", name: "selectionSimpleGrouped", title: "Simple (Grouped) options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions)
}
@@ -215,15 +214,15 @@ def inputSelectionPage() {
section("segmented") {
paragraph "segmented should only work if there are either 2 or 3 options to choose from"
input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", options: ["One"])
input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", options: ["One", "Two", "Three", "Four"])
input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"]))
input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"]))
paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected"
input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"])
input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"])
paragraph "specifying defaultValue still works with segmented selection elements"
input(type: "enum", name: "selectionSegmentedWithDefault", style: "segmented", title: "defaulted to 'two'", options: ["One", "Two", "Three"], defaultValue: "Two")
input(type: "enum", name: "selectionSegmentedWithDefault", title: "defaulted to 'two'", groupedOptions: addGroup(["One", "Two", "Three"]), defaultValue: "Two")
}
section("required: true") {
@@ -232,8 +231,6 @@ def inputSelectionPage() {
section("multiple: true") {
input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true)
input(type: "enum", name: "selectionMultipleDefault1", title: "This allows multiple selections with a single default", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: "an option")
input(type: "enum", name: "selectionMultipleDefault2", title: "This allows multiple selections with multiple defaults", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: ["an option", "another option"])
}
section("with image") {

View File

@@ -72,7 +72,7 @@ def authPage() {
log.debug "have LIFX access token"
def options = locationOptions() ?: []
def count = options.size().toString()
def count = options.size()
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
section("Select your location") {