Compare commits

..

1 Commits

Author SHA1 Message Date
larry tavano
dd13385a20 MSA-2147: samsung 2017-08-05 22:59:06 -07:00
4 changed files with 795 additions and 1429 deletions

View File

@@ -1,427 +0,0 @@
/**
* Aeon HEMv2
*
* Copyright 2014 Barry A. Burke
*
* 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.
*
*
* Aeon Home Energy Meter v2 (US)
*
* Author: Barry A. Burke
* Contributors: Brock Haymond: UI updates
*
* Genesys: Based off of Aeon Smart Meter Code sample provided by SmartThings (2013-05-30). Built on US model
* may also work on international versions (currently reports total values only)
*
* History:
*
* 2014-06-13: Massive OverHaul
* - Fixed Configuration (original had byte order of bitstrings backwards
* - Increased reporting frequency to 10s - note that values won't report unless they change
* (they will also report if they exceed limits defined in the settings - currently just using
* the defaults).
* - Added support for Volts & Amps monitoring (was only Power and Energy)
* - Added flexible tile display. Currently only used to show High and Low values since last
* reset (with time stamps).
* - All tiles are attributes, so that their values are preserved when you're not 'watching' the
* meter display
* - Values are formatted to Strings in zwaveEvent parser so that we don't lose decimal values
* in the tile label display conversion
* - Updated fingerprint to match Aeon Home Energy Monitor v2 deviceId & clusters
* - Added colors for Watts and Amps display
* - Changed time format to 24 hour
* 2014-06-17: Tile Tweaks
* - Reworked "decorations:" - current values are no longer "flat"
* - Added colors to current Watts (0-18000) & Amps (0-150)
* - Changed all colors to use same blue-green-orange-red as standard ST temperature guages
* 2014-06-18: Cost calculations
* - Added $/kWh preference
* 2014-09-07: Bug fix & Cleanup
* - Fixed "Unexpected Error" on Refresh tile - (added Refresh Capability)
* - Cleaned up low values - reset to ridiculously high value instead of null
* - Added poll() command/capability (just does a refresh)
*
* 2014-09-19 GUI Tweaks, HEM v1 alterations (from Brock Haymond)
* - Reworked all tiles for look, color, text formatting, & readability
*/
metadata {
// Automatically generated. Make future change here.
definition (
name: "Aeon HEMv2",
namespace: "smartthings",
category: "Green Living",
author: "Barry A. Burke"
)
{
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Sensor"
capability "Refresh"
capability "Polling"
capability "Battery"
attribute "energy", "string"
attribute "power", "string"
attribute "volts", "string"
attribute "amps", "string"
attribute "energyDisp", "string"
attribute "energyOne", "string"
attribute "energyTwo", "string"
attribute "powerDisp", "string"
attribute "powerOne", "string"
attribute "powerTwo", "string"
attribute "voltsDisp", "string"
attribute "voltsOne", "string"
attribute "voltsTwo", "string"
attribute "ampsDisp", "string"
attribute "ampsOne", "string"
attribute "ampsTwo", "string"
command "reset"
command "configure"
// v1 fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60"
fingerprint deviceId: "0x3101", inClusters: "0x70,0x32,0x60,0x85,0x56,0x72,0x86"
}
// simulator metadata
simulator {
for (int i = 0; i <= 10000; i += 1000) {
status "power ${i} W": new physicalgraph.zwave.Zwave().meterV1.meterReport(
scaledMeterValue: i, precision: 3, meterType: 33, scale: 2, size: 4).incomingMessage()
}
for (int i = 0; i <= 100; i += 10) {
status "energy ${i} kWh": new physicalgraph.zwave.Zwave().meterV1.meterReport(
scaledMeterValue: i, precision: 3, meterType: 33, scale: 0, size: 4).incomingMessage()
}
// TODO: Add data feeds for Volts and Amps
}
// tile definitions
tiles {
// Watts row
valueTile("powerDisp", "device.powerDisp") {
state (
"default",
label:'${currentValue}',
foregroundColors:[
[value: 1, color: "#000000"],
[value: 10000, color: "#ffffff"]
],
foregroundColor: "#000000",
backgroundColors:[
[value: "0 Watts", color: "#153591"],
[value: "500 Watts", color: "#1e9cbb"],
[value: "1000 Watts", color: "#90d2a7"],
[value: "1500 Watts", color: "#44b621"],
[value: "2000 Watts", color: "#f1d801"],
[value: "2500 Watts", color: "#d04e00"],
[value: "3000 Watts", color: "#bc2323"]
/*
[value: "0 Watts", color: "#153591"],
[value: "3000 Watts", color: "#1e9cbb"],
[value: "6000 Watts", color: "#90d2a7"],
[value: "9000 Watts", color: "#44b621"],
[value: "12000 Watts", color: "#f1d801"],
[value: "15000 Watts", color: "#d04e00"],
[value: "18000 Watts", color: "#bc2323"]
*/
]
)
}
valueTile("powerOne", "device.powerOne", decoration: "flat") {
state("default", label:'${currentValue}')
}
valueTile("powerTwo", "device.powerTwo", decoration: "flat") {
state("default", label:'${currentValue}')
}
// Power row
valueTile("energyDisp", "device.energyDisp") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyOne", "device.energyOne") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyTwo", "device.energyTwo") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
// Volts row
valueTile("voltsDisp", "device.voltsDisp") {
state "default", label: '${currentValue}', backgroundColors:[
[value: "115.6 Volts", color: "#bc2323"],
[value: "117.8 Volts", color: "#D04E00"],
[value: "120.0 Volts", color: "#44B621"],
[value: "122.2 Volts", color: "#D04E00"],
[value: "124.4 Volts", color: "#bc2323"]
]
}
valueTile("voltsOne", "device.voltsOne", decoration: "flat") {
state "default", label:'${currentValue}'
}
valueTile("voltsTwo", "device.voltsTwo", decoration: "flat") {
state "default", label:'${currentValue}'
}
// Amps row
valueTile("ampsDisp", "device.ampsDisp") {
state "default", label: '${currentValue}' , foregroundColor: "#000000", color: "#000000", backgroundColors:[
[value: "0 Amps", color: "#153591"],
[value: "25 Amps", color: "#1e9cbb"],
[value: "50 Amps", color: "#90d2a7"],
[value: "75 Amps", color: "#44b621"],
[value: "100 Amps", color: "#f1d801"],
[value: "125 Amps", color: "#d04e00"],
[value: "150 Amps", color: "#bc2323"]
]
}
valueTile("ampsOne", "device.ampsOne", decoration: "flat") {
state "default", label:'${currentValue}'
}
valueTile("ampsTwo", "device.ampsTwo", decoration: "flat") {
state "default", label:'${currentValue}'
}
// Controls row
standardTile("reset", "device.energy", inactiveLabel: false) {
state "default", label:'reset', action:"reset", icon: "st.Health & Wellness.health7"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat" ) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.power", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
// TODO: Add configurable delay button - Cycle through 10s, 30s, 1m, 5m, 60m, off?
main (["powerDisp","energyDisp","ampsDisp","voltsDisp"])
details([
"energyOne","energyDisp","energyTwo",
"powerOne","powerDisp","powerTwo",
//"ampsOne","ampsDisp","ampsTwo", // Comment out these two lines for HEMv!
//"voltsOne","voltsDisp","voltsTwo", // Comment out these two lines for HEMv1
"reset","refresh", "battery", "configure"
])
}
preferences {
input "kWhCost", "string", title: "\$/kWh (0.16)", defaultValue: "0.16" as String
}
}
def parse(String description) {
// log.debug "Parse received ${description}"
def result = null
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
if (result) log.debug "Parse returned ${result}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) {
// log.debug "zwaveEvent received ${cmd}"
def dispValue
def newValue
def timeString = new Date().format("h:mm a", location.timeZone)
if (cmd.meterType == 33) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkWh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "")
state.energyValue = newValue
BigDecimal costDecimal = newValue * ( kWhCost as BigDecimal)
def costDisplay = String.format("%3.2f",costDecimal)
sendEvent(name: "energyTwo", value: "Cost\n\$${costDisplay}", unit: "")
[name: "energy", value: newValue, unit: "kWh"]
}
} else if (cmd.scale == 1) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkVAh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "")
state.energyValue = newValue
[name: "energy", value: newValue, unit: "kVAh"]
}
}
else if (cmd.scale==2) {
newValue = Math.round( cmd.scaledMeterValue ) // really not worth the hassle to show decimals for Watts
if (newValue != state.powerValue) {
dispValue = newValue+"\nWatts"
sendEvent(name: "powerDisp", value: dispValue as String, unit: "")
if (newValue < state.powerLow) {
dispValue = "Low\n"+newValue+" W\n"+timeString
sendEvent(name: "powerOne", value: dispValue as String, unit: "")
state.powerLow = newValue
}
if (newValue > state.powerHigh) {
dispValue = "High\n"+newValue+" W\n"+timeString
sendEvent(name: "powerTwo", value: dispValue as String, unit: "")
state.powerHigh = newValue
}
state.powerValue = newValue
[name: "power", value: newValue, unit: "W"]
}
}
}
else if (cmd.meterType == 161) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
if (newValue != state.voltsValue) {
dispValue = String.format("%5.2f", newValue)+"\nVolts"
sendEvent(name: "voltsDisp", value: dispValue as String, unit: "")
if (newValue < state.voltsLow) {
dispValue = "Low\n"+String.format("%5.2f", newValue)+" V\n"+timeString
sendEvent(name: "voltsOne", value: dispValue as String, unit: "")
state.voltsLow = newValue
}
if (newValue > state.voltsHigh) {
dispValue = "High\n"+String.format("%5.2f", newValue)+" V\n"+timeString
sendEvent(name: "voltsTwo", value: dispValue as String, unit: "")
state.voltsHigh = newValue
}
state.voltsValue = newValue
[name: "volts", value: newValue, unit: "V"]
}
}
else if (cmd.scale==1) {
newValue = cmd.scaledMeterValue
if (newValue != state.ampsValue) {
dispValue = String.format("%5.2f", newValue)+"\nAmps"
sendEvent(name: "ampsDisp", value: dispValue as String, unit: "")
if (newValue < state.ampsLow) {
dispValue = "Low\n"+String.format("%5.2f", newValue)+" A\n"+timeString
sendEvent(name: "ampsOne", value: dispValue as String, unit: "")
state.ampsLow = newValue
}
if (newValue > state.ampsHigh) {
dispValue = "High\n"+String.format("%5.2f", newValue)+" A\n"+timeString
sendEvent(name: "ampsTwo", value: dispValue as String, unit: "")
state.ampsHigh = newValue
}
state.ampsValue = newValue
[name: "amps", value: newValue, unit: "A"]
}
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.unit = "%"
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
log.debug map
return map
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
log.debug "Unhandled event ${cmd}"
[:]
}
def refresh() {
delayBetween([
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
}
def poll() {
refresh()
}
def reset() {
log.debug "${device.name} reset"
state.powerHigh = 0
state.powerLow = 99999
state.ampsHigh = 0
state.ampsLow = 999
state.voltsHigh = 0
state.voltsLow = 999
def dateString = new Date().format("m/d/YY", location.timeZone)
def timeString = new Date().format("h:mm a", location.timeZone)
sendEvent(name: "energyOne", value: "Since\n"+dateString+"\n"+timeString, unit: "")
sendEvent(name: "powerOne", value: "", unit: "")
sendEvent(name: "voltsOne", value: "", unit: "")
sendEvent(name: "ampsOne", value: "", unit: "")
sendEvent(name: "ampsDisp", value: "", unit: "")
sendEvent(name: "voltsDisp", value: "", unit: "")
sendEvent(name: "powerDisp", value: "", unit: "")
sendEvent(name: "energyDisp", value: "", unit: "")
sendEvent(name: "energyTwo", value: "Cost\n--", unit: "")
sendEvent(name: "powerTwo", value: "", unit: "")
sendEvent(name: "voltsTwo", value: "", unit: "")
sendEvent(name: "ampsTwo", value: "", unit: "")
// No V1 available
def cmd = delayBetween( [
zwave.meterV2.meterReset().format(),
zwave.meterV2.meterGet(scale: 0).format()
])
cmd
}
def configure() {
// TODO: Turn on reporting for each leg of power - display as alternate view (Currently those values are
// returned as zwaveEvents...they probably aren't implemented in the core Meter device yet.
def cmd = delayBetween([
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 1).format(), // Enable selective reporting
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 50).format(), // Don't send unless watts have increased by 50
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 2, scaledConfigurationValue: 10).format(), // Or by 10% (these 3 are the default values
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 10).format(), // Average Watts & Amps
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 30).format(), // Every 30 Seconds
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 4).format(), // Average Voltage
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 150).format(), // every 2.5 minute
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), // Total kWh (cumulative)
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: 300).format() // every 5 minutes
])
log.debug cmd
cmd
}

View File

@@ -53,20 +53,21 @@ metadata {
}
}
standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "off", action:"switchMode", nextState:"...", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"...", icon: "st.thermostat.heat"
state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
state "...", label: "Updating...",nextState:"...", backgroundColor:"#ffffff"
state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.secondary.secondary"
state "to_cool", action:"switchMode", nextState:"...", icon: "st.secondary.secondary"
state "...", label: "...", action:"off", nextState:"off", icon: "st.secondary.secondary"
}
standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on"
state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate"
state "...", label: "Updating...", nextState:"...", backgroundColor:"#ffffff"
state "auto", action:"switchFanMode", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", icon: "st.thermostat.fan-on"
state "circulate", action:"switchFanMode", icon: "st.thermostat.fan-circulate"
}
standardTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") {
valueTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "humidity", label:'${currentValue}%', icon:"st.Weather.weather12"
}
standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
@@ -87,43 +88,37 @@ metadata {
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
}
standardTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:2, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "temperature"
details(["temperature", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint",
"coolingSetpoint", "raiseCoolSetpoint", "mode", "fanMode", "humidity", "thermostatOperatingState", "refresh"])
details(["temperature", "mode", "fanMode", "humidity", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint", "coolingSetpoint", "raiseCoolSetpoint", "refresh"])
}
}
def updated() {
// If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
}
initialize()
}
def installed() {
// Configure device
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
sendHubCommand(cmds)
runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
}
def updated() {
// If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
runIn(2, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
} else {
initialize()
}
initialize()
}
def initialize() {
unschedule()
// 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])
// Poll device for additional data that will be updated by refresh tile
poll()
refresh()
}
def parse(String description)
@@ -156,43 +151,35 @@ def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceCmdEncap
}
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) {
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def sendCmd = []
def unit = getTemperatureScale()
def cmdScale = cmd.scale == 1 ? "F" : "C"
def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
// Save device scale, precision, scale as they are used when enforcing setpoint limits
if (cmd.setpointType == 1 || cmd.setpointType == 2) {
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
}
switch (cmd.setpointType) {
case 1: // "heatingSetpoint"
state.deviceHeatingSetpoint = cmd.scaledValue
if (state.targetHeatingSetpoint) {
state.targetHeatingSetpoint = null
sendEvent(name: "heatingSetpoint", value: setpoint, unit: getTemperatureScale())
} else if (mode != "cool") {
// if mode is cool heatingSetpoint can't be changed on device, disregard update
// else update heatingSetpoint and enforce limits on coolingSetpoint
updateEnforceSetpointLimits("heatingSetpoint", setpoint)
case 1:
//map1.name = "heatingSetpoint"
sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("heatingSetpoint", setpoint)
// Enforce coolingSetpoint limits, as device doesn't
if (setpoint > getTempInLocalScale("coolingSetpoint")) {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(sendCmd)
}
break;
case 2: // "coolingSetpoint"
state.deviceCoolingSetpoint = cmd.scaledValue
if (state.targetCoolingSetpoint) {
state.targetCoolingSetpoint = null
sendEvent(name: "coolingSetpoint", value: setpoint, unit: getTemperatureScale())
} else if (mode != "heat" || mode != "emergency heat") {
// if mode is heat or emergency heat coolingSetpoint can't be changed on device, disregard update
// else update coolingSetpoint and enforce limits on heatingSetpoint
updateEnforceSetpointLimits("coolingSetpoint", setpoint)
case 2:
//map1.name = "coolingSetpoint"
sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("coolingSetpoint", setpoint)
// Enforce heatingSetpoint limits, as device doesn't
if (setpoint < getTempInLocalScale("heatingSetpoint")) {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
sendHubCommand(sendCmd)
}
break;
default:
@@ -200,6 +187,28 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpo
return
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
}
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
}
// Just set to average of heating + cooling for mode off and auto
if (mode == "off" || mode == "auto") {
thermostatSetpoint = getTempInLocalScale((heatingSetpoint + coolingSetpoint)/2, scale)
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: scale)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
@@ -208,7 +217,6 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelR
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) {
map.name = "humidity"
map.unit = "%"
@@ -223,7 +231,6 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelR
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
@@ -233,7 +240,7 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelR
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) {
def map = [name: "thermostatOperatingState"]
def map = [name: "thermostatOperatingState" ]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
@@ -295,30 +302,15 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeRepor
map.value = "auto"
break
}
state.lastTriedMode = map.value
sendEvent(map)
// Now that mode and temperature is known we can request setpoints in correct order
// Also makes sure operating state is in sync as it isn't being reported when changed
def cmds = []
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def currentTemperature = getTempInLocalScale("temperature")
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
if (map.value == "cool" || ((map.value == "auto" || map.value == "off") && (currentTemperature > (heatingSetpoint + coolingSetpoint)/2))) {
// request cooling setpoint first
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
} else {
// request heating setpoint first
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
}
sendHubCommand(cmds)
updateThermostatSetpoint(null, null)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "auto"
break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
@@ -328,6 +320,7 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanMod
map.value = "circulate"
break
}
state.lastTriedFanMode = map.value
sendEvent(map)
}
@@ -405,65 +398,64 @@ def poll() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 2).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // humidity
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
def time = getTimeAndDay()
log.debug "time: $time"
if (time) {
cmds << new physicalgraph.device.HubAction(zwave.clockV1.clockSet(time).format())
}
// ThermostatModeReport will spawn request for operating state and setpoints so request this last
// this as temperature and mode is needed to determine which setpoints should be requested first
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
// Add 2 seconds delay between each command to avoid flooding the Z-Wave network choking the hub
sendHubCommand(cmds, 2000)
// Add 3 seconds delay between each command to avoid flooding the Z-Wave network choking the hub
sendHubCommand(cmds, 3000)
}
def raiseHeatingSetpoint() {
alterSetpoint(true, "heatingSetpoint")
alterSetpoint(null, true, "heatingSetpoint")
}
def lowerHeatingSetpoint() {
alterSetpoint(false, "heatingSetpoint")
alterSetpoint(null, false, "heatingSetpoint")
}
def raiseCoolSetpoint() {
alterSetpoint(true, "coolingSetpoint")
alterSetpoint(null, true, "coolingSetpoint")
}
def lowerCoolSetpoint() {
alterSetpoint(false, "coolingSetpoint")
alterSetpoint(null, false, "coolingSetpoint")
}
// Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
def alterSetpoint(raise, setpoint) {
def alterSetpoint(degrees, raise, setpoint) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def targetValue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
def targetvalue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
def delta = (locationScale == "F") ? 1 : 0.5
targetValue += raise ? delta : - delta
def data = enforceSetpointLimits(setpoint, [targetValue: targetValue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint], raise)
if (raise != null) {
targetvalue += raise ? delta : - delta
} else if (degrees) {
targetvalue = degrees
} else {
log.warn "alterSetpoint called with neither up/down/degree information"
return
}
def data = enforceSetpointLimits(setpoint, [targetvalue: targetvalue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
// update UI without waiting for the device to respond, this to give user a smoother UI experience
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
if (data.targetHeatingSetpoint) {
sendEvent("name": "heatingSetpoint", "value": getTempInLocalScale(data.targetHeatingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
sendEvent("name": "heatingSetpoint", "value": data.targetHeatingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
runIn(4, "updateHeatingSetpoint", [data: data, overwrite: true])
}
if (data.targetCoolingSetpoint) {
sendEvent("name": "coolingSetpoint", "value": getTempInLocalScale(data.targetCoolingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetHeatingSetpoint && data.targetCoolingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
runIn(5, "updateCoolingSetpoint", [data: data, overwrite: true])
sendEvent("name": "coolingSetpoint", "value": data.targetCoolingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
runIn(4, "updateCoolingSetpoint", [data: data, overwrite: true])
}
}
@@ -475,138 +467,69 @@ def updateCoolingSetpoint(data) {
updateSetpoints(data)
}
def updateEnforceSetpointLimits(setpoint, setpointValue) {
def heatingSetpoint = (setpoint == "heatingSetpoint") ? setpointValue : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? setpointValue : getTempInLocalScale("coolingSetpoint")
sendEvent(name: setpoint, value: setpointValue, unit: getTemperatureScale(), displayed: false)
updateThermostatSetpoint(setpoint, setpointValue)
// Enforce coolingSetpoint limits, as device doesn't
def data = enforceSetpointLimits(setpoint, [targetValue: setpointValue,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
data.targetHeatingSetpoint = null
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
data.targetCoolingSetpoint = null
}
if (data.targetHeatingSetpoint != null || data.targetCoolingSetpoint != null) {
updateSetpoints(data)
}
}
def enforceSetpointLimits(setpoint, data, raise = null) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
// min/max with 3°F/2°C deadband consideration
def minSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(35, "F") : getTempInDeviceScale(38, "F")
def maxSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(92, "F") : getTempInDeviceScale(95, "F")
def deadband = (deviceScale == "F") ? 3 : 2
def delta = (locationScale == "F") ? 1 : 0.5
def targetValue = getTempInDeviceScale(data.targetValue, locationScale)
def enforceSetpointLimits(setpoint, data) {
// Enforce max/min for setpoints
def maxSetpoint = getTempInLocalScale(95, "F")
def minSetpoint = getTempInLocalScale(35, "F")
def targetvalue = data.targetvalue
def heatingSetpoint = null
def coolingSetpoint = null
// Enforce min/mix for setpoints
if (targetValue > maxSetpoint) {
heatingSetpoint = (setpoint == "heatingSetpoint") ? maxSetpoint : getTempInDeviceScale(data.heatingSetpoint, locationScale)
coolingSetpoint = (setpoint == "heatingSetpoint") ? maxSetpoint + deadband : maxSetpoint
} else if (targetValue < minSetpoint) {
heatingSetpoint = (setpoint == "coolingSetpoint") ? minSetpoint - deadband : minSetpoint
coolingSetpoint = (setpoint == "coolingSetpoint") ? minSetpoint : getTempInDeviceScale(data.coolingSetpoint, locationScale)
if (targetvalue > maxSetpoint) {
targetvalue = maxSetpoint
} else if (targetvalue < minSetpoint) {
targetvalue = minSetpoint
}
// Enforce deadband between setpoints
if (setpoint == "heatingSetpoint" && !coolingSetpoint) {
// Note if new value is same as old value we need to move it in the direction the user wants to change it, 1°F or 0.5°C,
heatingSetpoint = (targetValue != getTempInDeviceScale(data.heatingSetpoint, locationScale) || !raise) ?
targetValue : (raise ? targetValue + delta : targetValue - delta)
coolingSetpoint = (heatingSetpoint + deadband > getTempInDeviceScale(data.coolingSetpoint, locationScale)) ? heatingSetpoint + deadband : null
// Enforce limits, for now make sure heating <= cooling, and cooling >= heating
if (setpoint == "heatingSetpoint") {
heatingSetpoint = targetvalue
coolingSetpoint = (heatingSetpoint > data.coolingSetpoint) ? heatingSetpoint : null
}
if (setpoint == "coolingSetpoint" && !heatingSetpoint) {
coolingSetpoint = (targetValue != getTempInDeviceScale(data.coolingSetpoint, locationScale) || !raise) ?
targetValue : (raise ? targetValue + delta : targetValue - delta)
heatingSetpoint = (coolingSetpoint - deadband < getTempInDeviceScale(data.heatingSetpoint, locationScale)) ? coolingSetpoint - deadband : null
if (setpoint == "coolingSetpoint") {
coolingSetpoint = targetvalue
heatingSetpoint = (coolingSetpoint < data.heatingSetpoint) ? coolingSetpoint : null
}
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
}
def setHeatingSetpoint(degrees) {
if (degrees) {
state.heatingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
def data = enforceSetpointLimits("heatingSetpoint",
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
updateSetpoints(data)
}
}
def setCoolingSetpoint(degrees) {
if (degrees) {
state.coolingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
def data = enforceSetpointLimits("coolingSetpoint",
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
updateSetpoints(data)
}
}
def updateSetpoints() {
def deviceScale = (state.scale == 1) ? "F" : "C"
def data = [targetHeatingSetpoint: null, targetCoolingSetpoint: null]
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
if (state.heatingSetpoint) {
data = enforceSetpointLimits("heatingSetpoint", [targetValue: state.heatingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
}
if (state.coolingSetpoint) {
heatingSetpoint = data.targetHeatingSetpoint ? getTempInLocalScale(data.targetHeatingSetpoint, deviceScale) : heatingSetpoint
coolingSetpoint = data.targetCoolingSetpoint ? getTempInLocalScale(data.targetCoolingSetpoint, deviceScale) : coolingSetpoint
data = enforceSetpointLimits("coolingSetpoint", [targetValue: state.coolingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
data.targetHeatingSetpoint = data.targetHeatingSetpoint ?: heatingSetpoint
}
state.heatingSetpoint = null
state.coolingSetpoint = null
updateSetpoints(data)
}
def updateSetpoints(data) {
unschedule("updateSetpoints")
def cmds = []
if (data.targetHeatingSetpoint) {
state.targetHeatingSetpoint = data.targetHeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: data.targetHeatingSetpoint).format())
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetHeatingSetpoint)).format())
}
if (data.targetCoolingSetpoint) {
state.targetCoolingSetpoint = data.targetCoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: data.targetCoolingSetpoint).format())
}
// Also make sure temperature and operating state is in sync
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
if (data.targetHeatingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
}
if (data.targetCoolingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetCoolingSetpoint)).format())
}
// Always request both setpoints in case thermostat changed both
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(cmds)
}
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
} else if (mode == "auto" || mode == "off") {
// Set thermostatSetpoint to the setpoint closest to the current temperature
def currentTemperature = getTempInLocalScale("temperature")
if (currentTemperature > (heatingSetpoint + coolingSetpoint)/2) {
thermostatSetpoint = coolingSetpoint
}
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())
def convertToDeviceScale(setpoint) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == locationScale) ? setpoint :
(deviceScale == "F" ? celsiusToFahrenheit(setpoint.toBigDecimal()) : roundC(fahrenheitToCelsius(setpoint.toBigDecimal())))
}
/**
@@ -620,68 +543,54 @@ def ping() {
def switchMode() {
def currentMode = device.currentValue("thermostatMode")
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = state.supportedModes
if (supportedModes) {
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
def nextMode = next(lastTriedMode)
setThermostatMode(nextMode)
state.lastTriedMode = nextMode
} else {
log.warn "supportedModes not defined"
getSupportedModes()
}
}
def switchToMode(nextMode) {
def supportedModes = state.supportedModes
if (supportedModes) {
if (supportedModes.contains(nextMode)) {
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
if (supportedModes && supportedModes.contains(nextMode)) {
setThermostatMode(nextMode)
state.lastTriedMode = nextMode
} else {
log.warn "supportedModes not defined"
getSupportedModes()
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
}
def getSupportedModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
sendHubCommand(cmds)
}
def switchFanMode() {
def currentMode = device.currentValue("thermostatFanMode")
def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
def nextMode = next(lastTriedMode)
setThermostatFanMode(nextMode)
state.lastTriedFanMode = nextMode
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
}
}
def switchToFanMode(nextMode) {
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
if (supportedFanModes.contains(nextMode)) {
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
if (supportedFanModes && supportedFanModes.contains(nextMode)) {
setThermostatFanMode(nextMode)
state.lastTriedFanMode = nextMode
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
}
def getSupportedFanModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
sendHubCommand(cmds)
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def getModeMap() { [
@@ -693,12 +602,8 @@ def getModeMap() { [
]}
def setThermostatMode(String value) {
switchToMode(value)
}
def setThermostatMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
sendHubCommand(cmds)
}
@@ -710,12 +615,8 @@ def getFanModeMap() { [
]}
def setThermostatFanMode(String value) {
switchToFanMode(value)
}
def setThermostatFanMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
sendHubCommand(cmds)
}
@@ -774,28 +675,8 @@ def getTempInLocalScale(state) {
// get/convert temperature to current local scale
def getTempInLocalScale(temp, scale) {
if (temp && scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
return 0
}
def getTempInDeviceScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInDeviceScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
}
def getTempInDeviceScale(temp, scale) {
if (temp && scale) {
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == scale) ? temp :
(deviceScale == "F" ? celsiusToFahrenheit(temp).toDouble().round(0).toInteger() : roundC(fahrenheitToCelsius(temp)))
}
return 0
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
def roundC (tempC) {

View File

@@ -16,6 +16,7 @@ metadata {
capability "Actuator"
capability "Temperature Measurement"
capability "Thermostat"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Health Check"
@@ -24,175 +25,209 @@ metadata {
command "switchMode"
command "switchFanMode"
command "lowerHeatingSetpoint"
command "raiseHeatingSetpoint"
command "lowerCoolSetpoint"
command "raiseCoolSetpoint"
command "quickSetCool"
command "quickSetHeat"
fingerprint deviceId: "0x08"
fingerprint inClusters: "0x43,0x40,0x44,0x31"
fingerprint mfr:"0039", prod:"0011", model:"0001", deviceJoinName: "Honeywell Z-Wave Thermostat"
}
// simulator metadata
simulator {
status "off" : "command: 4003, payload: 00"
status "heat" : "command: 4003, payload: 01"
status "cool" : "command: 4003, payload: 02"
status "auto" : "command: 4003, payload: 03"
status "emergencyHeat" : "command: 4003, payload: 04"
status "auto" : "command: 4403, payload: 00" // "fanAuto"
status "on" : "command: 4403, payload: 01" // "fanOn"
status "circulate" : "command: 4403, payload: 06" // "fanCirculate
status "heat 60" : "command: 4303, payload: 01 09 3C"
status "heat 68" : "command: 4303, payload: 01 09 44"
status "heat 72" : "command: 4303, payload: 01 09 48"
status "cool 72" : "command: 4303, payload: 02 09 48"
status "cool 76" : "command: 4303, payload: 02 09 4C"
status "cool 80" : "command: 4303, payload: 02 09 50"
status "temp 58" : "command: 3105, payload: 01 2A 02 44"
status "temp 62" : "command: 3105, payload: 01 2A 02 6C"
status "temp 70" : "command: 3105, payload: 01 2A 02 BC"
status "temp 74" : "command: 3105, payload: 01 2A 02 E4"
status "temp 78" : "command: 3105, payload: 01 2A 03 0C"
status "temp 82" : "command: 3105, payload: 01 2A 03 34"
status "idle" : "command: 4203, payload: 00"
status "heating" : "command: 4203, payload: 01"
status "cooling" : "command: 4203, payload: 02"
status "fan only" : "command: 4203, payload: 03"
status "pending heat" : "command: 4203, payload: 04"
status "pending cool" : "command: 4203, payload: 05"
status "vent economizer": "command: 4203, payload: 06"
// reply messages
reply "2502": "command: 2503, payload: FF"
}
tiles {
multiAttributeTile(name:"temperature", type:"generic", width:3, height:2, canChangeIcon: true) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temperature", label:'${currentValue}°', icon: "st.alarm.temperature.normal",
backgroundColors:[
// Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, 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"]
]
)
}
// Using standardTile instead of valueTile as it renders the icon better
standardTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', icon: "st.thermostat.ac.air-conditioning",
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"]
]
)
}
standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "off", action:"switchMode", nextState:"...", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"...", icon: "st.thermostat.heat"
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
state "...", label: "Updating...",nextState:"...", backgroundColor:"#ffffff"
state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat"
state "to_cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "...", label: "...", action:"off", nextState:"off"
}
standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on"
state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate"
state "...", label: "Updating...", nextState:"...", backgroundColor:"#ffffff"
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto" // "fanAuto"
state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on" // "fanOn"
state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate" // "fanCirculate"
state "...", label: "...", nextState:"..."
}
standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"lowerHeatingSetpoint", icon:"st.thermostat.thermostat-left"
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#d04e00"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", label:'${currentValue}° heat', backgroundColor:"#ffffff"
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff"
}
standardTile("raiseHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseHeatingSetpoint", icon:"st.thermostat.thermostat-right"
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#1e9cbb"
}
standardTile("lowerCoolSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "coolingSetpoint", action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-left"
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "coolingSetpoint", label:'${currentValue}° cool', backgroundColor:"#ffffff"
}
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
}
valueTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:1, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "temperature"
details(["temperature", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint",
"coolingSetpoint", "raiseCoolSetpoint", "mode", "fanMode", "thermostatOperatingState", "refresh"])
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"])
}
}
def installed() {
// Configure device
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
sendHubCommand(cmds)
runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
def installed(){
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format()))
initialize()
}
def updated() {
// If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
runIn(2, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
} else {
initialize()
}
def updated(){
initialize()
}
def initialize() {
// 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])
unschedule()
if (getDataValue("manufacturer") != "Honeywell") {
runEvery5Minutes("poll") // This is not necessary for Honeywell Z-wave, but could be for other Z-wave thermostats
}
poll()
runEvery5Minutes("refresh")
refresh()
}
def parse(String description)
{
def result = null
if (description == "updated") {
} else {
def zwcmd = zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3])
if (zwcmd) {
result = zwaveEvent(zwcmd)
} else {
log.debug "$device.displayName couldn't parse $description"
def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3])))
if (!map) {
return null
}
def result = [map]
if (map.isStateChange && map.name in ["heatingSetpoint","coolingSetpoint","thermostatMode"]) {
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
map.data = [supportedThermostatModes:state.supportedThermostatModes]
if (map.value == "cool") {
map2.value = device.latestValue("coolingSetpoint")
log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}"
}
else {
map2.value = device.latestValue("heatingSetpoint")
log.info "THERMOSTAT, latest heating setpoint = ${map2.value}"
}
}
else {
def mode = device.latestValue("thermostatMode")
log.info "THERMOSTAT, latest mode = ${mode}"
if ((map.name == "heatingSetpoint" && mode == "heat") || (map.name == "coolingSetpoint" && mode == "cool")) {
map2.value = map.value
map2.unit = map.unit
}
}
if (map2.value != null) {
log.debug "THERMOSTAT, adding setpoint event: $map"
result << createEvent(map2)
}
} else if (map.name == "thermostatFanMode" && map.isStateChange) {
state.lastTriedFanMode = map.value
map.data = [supportedThermostatFanModes: state.supportedThermostatFanModes]
}
if (!result) {
return []
}
return [result]
log.debug "Parse returned $result"
result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) {
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
def unit = getTemperatureScale()
def map = [:]
map.value = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.displayed = false
switch (cmd.setpointType) {
case 1:
sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("heatingSetpoint", setpoint)
map.name = "heatingSetpoint"
break;
case 2:
sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("coolingSetpoint", setpoint)
map.name = "coolingSetpoint"
break;
default:
log.debug "unknown setpointType $cmd.setpointType"
return
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
// Make sure return value is not result from above expresion
return 0
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) {
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd)
{
def map = [:]
if (cmd.sensorType == 1) {
map.value = getTempInLocalScale(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C")
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
sendEvent(map)
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd) {
def map = [name: "thermostatOperatingState"]
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd)
{
def map = [:]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
@@ -216,9 +251,8 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.Thermosta
map.value = "vent economizer"
break
}
// Makes sure we have the correct thermostat mode
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format()))
sendEvent(map)
map.name = "thermostatOperatingState"
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
@@ -234,11 +268,11 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanSt
map.value = "running high"
break
}
sendEvent(map)
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [name: "thermostatMode", data:[supportedThermostatModes: state.supportedModes]]
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
@@ -256,24 +290,26 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeRepor
map.value = "auto"
break
}
sendEvent(map)
updateThermostatSetpoint(null, null)
map.name = "thermostatMode"
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
def map = [:]
switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "auto"
map.value = "auto" // "fanAuto"
break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
map.value = "on"
map.value = "on" // "fanOn"
break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
map.value = "circulate"
map.value = "circulate" // "fanCirculate"
break
}
sendEvent(map)
map.name = "thermostatFanMode"
map.displayed = false
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
@@ -284,34 +320,24 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSuppo
if(cmd.auto) { supportedModes << "auto" }
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
state.supportedModes = supportedModes
state.supportedThermostatModes = supportedModes
sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false)
return [:]
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = []
if(cmd.auto) { supportedFanModes << "auto" }
if(cmd.circulation) { supportedFanModes << "circulate" }
if(cmd.low) { supportedFanModes << "on" }
if(cmd.auto) { supportedFanModes << "auto" } // "fanAuto "
if(cmd.circulation) { supportedFanModes << "circulate" } // "fanCirculate"
if(cmd.low) { supportedFanModes << "on" } // "fanOn"
state.supportedFanModes = supportedFanModes
state.supportedThermostatFanModes = supportedFanModes
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false)
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
if (cmd.manufacturerName) {
updateDataValue("manufacturer", cmd.manufacturerName)
}
if (cmd.productTypeId) {
updateDataValue("productTypeId", cmd.productTypeId.toString())
}
if (cmd.productId) {
updateDataValue("productId", cmd.productId.toString())
}
return [:]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave BasicReport: $cmd"
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
@@ -320,16 +346,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Command Implementations
def refresh() {
// Only allow refresh every 2 minutes to prevent flooding the Zwave network
def timeNow = now()
if (!state.refreshTriggeredAt || (2 * 60 * 1000 < (timeNow - state.refreshTriggeredAt))) {
state.refreshTriggeredAt = timeNow
// use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
runIn(2, "poll", [overwrite: true])
}
}
def poll() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
@@ -342,152 +358,64 @@ def poll() {
sendHubCommand(cmds)
}
def raiseHeatingSetpoint() {
alterSetpoint(true, "heatingSetpoint")
def quickSetHeat(degrees) {
setHeatingSetpoint(degrees, 1000)
}
def lowerHeatingSetpoint() {
alterSetpoint(false, "heatingSetpoint")
def setHeatingSetpoint(degrees, delay = 30000) {
setHeatingSetpoint(degrees.toDouble(), delay)
}
def raiseCoolSetpoint() {
alterSetpoint(true, "coolingSetpoint")
def setHeatingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setHeatingSetpoint($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 lowerCoolSetpoint() {
alterSetpoint(false, "coolingSetpoint")
def quickSetCool(degrees) {
setCoolingSetpoint(degrees, 1000)
}
// Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
def alterSetpoint(raise, setpoint) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def targetValue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
def delta = (locationScale == "F") ? 1 : 0.5
targetValue += raise ? delta : - delta
def data = enforceSetpointLimits(setpoint, [targetValue: targetValue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
// update UI without waiting for the device to respond, this to give user a smoother UI experience
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
if (data.targetHeatingSetpoint) {
sendEvent("name": "heatingSetpoint", "value": getTempInLocalScale(data.targetHeatingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetCoolingSetpoint) {
sendEvent("name": "coolingSetpoint", "value": getTempInLocalScale(data.targetCoolingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetHeatingSetpoint && data.targetCoolingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
runIn(5, "updateCoolingSetpoint", [data: data, overwrite: true])
}
def setCoolingSetpoint(degrees, delay = 30000) {
setCoolingSetpoint(degrees.toDouble(), delay)
}
def updateHeatingSetpoint(data) {
updateSetpoints(data)
}
def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setCoolingSetpoint($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 updateCoolingSetpoint(data) {
updateSetpoints(data)
}
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
def enforceSetpointLimits(setpoint, data) {
def locationScale = getTemperatureScale()
def minSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(40, "F") : getTempInDeviceScale(50, "F")
def maxSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(90, "F") : getTempInDeviceScale(99, "F")
def deadband = (state.scale == 1) ? 3 : 2 // 3°F, 2°C
def targetValue = getTempInDeviceScale(data.targetValue, locationScale)
def heatingSetpoint = null
def coolingSetpoint = null
// Enforce min/mix for setpoints
if (targetValue > maxSetpoint) {
targetValue = maxSetpoint
} else if (targetValue < minSetpoint) {
targetValue = minSetpoint
}
// Enforce 3 degrees F deadband between setpoints
if (setpoint == "heatingSetpoint") {
heatingSetpoint = targetValue
coolingSetpoint = (heatingSetpoint + deadband > getTempInDeviceScale(data.coolingSetpoint, locationScale)) ? heatingSetpoint + deadband : null
}
if (setpoint == "coolingSetpoint") {
coolingSetpoint = targetValue
heatingSetpoint = (coolingSetpoint - deadband < getTempInDeviceScale(data.heatingSetpoint, locationScale)) ? coolingSetpoint - deadband : null
}
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
}
def setHeatingSetpoint(degrees) {
if (degrees) {
state.heatingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
}
}
def setCoolingSetpoint(degrees) {
if (degrees) {
state.coolingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
}
}
def updateSetpoints() {
def deviceScale = (state.scale == 1) ? "F" : "C"
def data = [targetHeatingSetpoint: null, targetCoolingSetpoint: null]
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
if (state.heatingSetpoint) {
data = enforceSetpointLimits("heatingSetpoint", [targetValue: state.heatingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
}
if (state.coolingSetpoint) {
heatingSetpoint = data.targetHeatingSetpoint ? getTempInLocalScale(data.targetHeatingSetpoint, deviceScale) : heatingSetpoint
coolingSetpoint = data.targetCoolingSetpoint ? getTempInLocalScale(data.targetCoolingSetpoint, deviceScale) : coolingSetpoint
data = enforceSetpointLimits("coolingSetpoint", [targetValue: state.coolingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
data.targetHeatingSetpoint = data.targetHeatingSetpoint ?: heatingSetpoint
}
state.heatingSetpoint = null
state.coolingSetpoint = null
updateSetpoints(data)
}
def updateSetpoints(data) {
def cmds = []
if (data.targetHeatingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: data.targetHeatingSetpoint).format())
}
if (data.targetCoolingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: data.targetCoolingSetpoint).format())
}
sendHubCommand(cmds)
}
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
} else if (mode == "auto" || mode == "off") {
// Set thermostatSetpoint to the setpoint closest to the current temperature
def currentTemperature = getTempInLocalScale("temperature")
if (currentTemperature > (heatingSetpoint + coolingSetpoint)/2) {
thermostatSetpoint = coolingSetpoint
}
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()
], delay)
}
/**
@@ -495,74 +423,76 @@ def updateThermostatSetpoint(setpoint, value) {
* */
def ping() {
log.debug "ping() called"
// Just get Operating State there's no need to flood more commands
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()))
poll()
}
def modes() {
return state.supportedThermostatModes
}
def switchMode() {
def currentMode = device.currentValue("thermostatMode")
def supportedModes = state.supportedModes
if (supportedModes) {
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedModes not defined"
getSupportedModes()
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: ["off"]
def supportedModes = getDataByName("supportedThermostatModes")
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "off") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], 1000)
}
def switchToMode(nextMode) {
def supportedModes = state.supportedModes
if (supportedModes) {
if (supportedModes.contains(nextMode)) {
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
def supportedModes = getDataByName("supportedThermostatModes")
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if (nextMode in modes()) {
state.lastTriedMode = nextMode
"$nextMode"()
} else {
log.warn "supportedModes not defined"
getSupportedModes()
log.debug("no mode method '$nextMode'")
}
}
def getSupportedModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
sendHubCommand(cmds)
}
def switchFanMode() {
def currentMode = device.currentValue("thermostatFanMode")
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: ["off"]
def supportedModes = getDataByName("supportedThermostatFanModes") ?: ["auto", "on"]
def modeOrder = state.supportedThermostatFanModes
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto"
nextMode = next(nextMode)
}
switchToFanMode(nextMode)
}
def switchToFanMode(nextMode) {
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
if (supportedFanModes.contains(nextMode)) {
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
def supportedFanModes = getDataByName("supportedThermostatFanModes")
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
def returnCommand
if (nextMode == "auto") { // "fanAuto"
returnCommand = fanAuto()
} else if (nextMode == "on") { // "fanOn"
returnCommand = fanOn()
} else if (nextMode == "circulate") { // "fanCirculate"
returnCommand = fanCirculate()
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
log.debug("no fan mode '$nextMode'")
}
if(returnCommand) state.lastTriedFanMode = nextMode
returnCommand
}
def getSupportedFanModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
sendHubCommand(cmds)
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def getModeMap() { [
@@ -574,14 +504,10 @@ def getModeMap() { [
]}
def setThermostatMode(String value) {
switchToMode(value)
}
def setThermostatMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
sendHubCommand(cmds)
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def getFanModeMap() { [
@@ -591,83 +517,69 @@ def getFanModeMap() { [
]}
def setThermostatFanMode(String value) {
switchToFanMode(value)
}
def setThermostatFanMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
sendHubCommand(cmds)
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def off() {
switchToMode("off")
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def heat() {
switchToMode("heat")
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def emergencyHeat() {
switchToMode("emergency heat")
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def cool() {
switchToMode("cool")
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def auto() {
switchToMode("auto")
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def fanOn() {
switchToFanMode("on")
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanAuto() {
switchToFanMode("auto")
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanCirculate() {
switchToFanMode("circulate")
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
// Get stored temperature from currentState in current local scale
def getTempInLocalScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInLocalScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
private getStandardDelay() {
1000
}
// get/convert temperature to current local scale
def getTempInLocalScale(temp, scale) {
if (temp && scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
return 0
}
def getTempInDeviceScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInDeviceScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
}
def getTempInDeviceScale(temp, scale) {
if (temp && scale) {
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == scale) ? temp :
(deviceScale == "F" ? celsiusToFahrenheit(temp) : roundC(fahrenheitToCelsius(temp)))
}
return 0
}
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}

View File

@@ -1,344 +1,344 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Button Controller
*
* Author: SmartThings
* Date: 2014-5-21
*/
definition(
name: "Button Controller",
namespace: "smartthings",
author: "SmartThings",
description: "Control devices with buttons like the Aeon Labs Minimote",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
)
preferences {
page(name: "selectButton")
for (def i=1; i<=8; i++) {
page(name: "configureButton$i")
}
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def selectButton() {
dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
section {
input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
}
section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
}
}
}
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)
}
def configureButton2() {
createPage(2)
}
def configureButton3() {
createPage(3)
}
def configureButton4() {
createPage(4)
}
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
}
def getButtonSections(buttonNumber) {
return {
section("Lights") {
input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
}
section("Locks") {
input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
}
section("Sonos") {
input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
}
section("Modes") {
input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
section("Hello Home Actions") {
log.trace phrases
input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
}
}
section("Sirens") {
input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
}
section("Custom Message") {
input "textMessage_${buttonNumber}", "text", title: "Message", required: false
}
section("Push Notifications") {
input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
}
section("Sms Notifications") {
input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
}
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
subscribe(buttonDevice, "button", buttonEvent)
}
def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
}
def buttonConfigured(idx) {
return settings["lights_$idx_pushed"] ||
settings["locks_$idx_pushed"] ||
settings["sonos_$idx_pushed"] ||
settings["mode_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["sirens_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["phone_$idx_pushed"]
}
def buttonEvent(evt){
if(allOk) {
def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug "button: $buttonNumber, value: $value"
def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
if(recentEvents.size <= 1){
switch(buttonNumber) {
case ~/.*1.*/:
executeHandlers(1, value)
break
case ~/.*2.*/:
executeHandlers(2, value)
break
case ~/.*3.*/:
executeHandlers(3, value)
break
case ~/.*4.*/:
executeHandlers(4, value)
break
}
} else {
log.debug "Found recent button press events for $buttonNumber with value $value"
}
}
}
def executeHandlers(buttonNumber, value) {
log.debug "executeHandlers: $buttonNumber - $value"
def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights)
def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks)
def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos)
def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode)
def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase)
def textMessage = findMsg('textMessage', buttonNumber)
def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens)
}
def find(type, buttonNumber, value) {
def preferenceName = type + "_" + buttonNumber + "_" + value
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def findMsg(type, buttonNumber) {
def preferenceName = type + "_" + buttonNumber
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def toggle(devices) {
log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
if (devices*.currentValue('switch').contains('on')) {
devices.off()
}
else if (devices*.currentValue('switch').contains('off')) {
devices.on()
}
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}
else {
devices.on()
}
}
def changeMode(mode) {
log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
if (location.mode != mode && location.modes?.find { it.name == mode }) {
setLocationMode(mode)
}
}
// execution filter methods
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting, location.timeZone).time
def stop = timeToday(ending, location.timeZone).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}
private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Button Controller
*
* Author: SmartThings
* Date: 2014-5-21
*/
definition(
name: "Button Controller",
namespace: "smartthings",
author: "SmartThings",
description: "Control devices with buttons like the Aeon Labs Minimote",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
)
preferences {
page(name: "selectButton")
for (def i=1; i<=8; i++) {
page(name: "configureButton$i")
}
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def selectButton() {
dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
section {
input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
}
section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
}
}
}
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)
}
def configureButton2() {
createPage(2)
}
def configureButton3() {
createPage(3)
}
def configureButton4() {
createPage(4)
}
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
}
def getButtonSections(buttonNumber) {
return {
section("Lights") {
input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
}
section("Locks") {
input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
}
section("Sonos") {
input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
}
section("Modes") {
input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
section("Hello Home Actions") {
log.trace phrases
input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
}
}
section("Sirens") {
input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
}
section("Custom Message") {
input "textMessage_${buttonNumber}", "text", title: "Message", required: false
}
section("Push Notifications") {
input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
}
section("Sms Notifications") {
input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
}
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
subscribe(buttonDevice, "button", buttonEvent)
}
def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
}
def buttonConfigured(idx) {
return settings["lights_$idx_pushed"] ||
settings["locks_$idx_pushed"] ||
settings["sonos_$idx_pushed"] ||
settings["mode_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["sirens_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["phone_$idx_pushed"]
}
def buttonEvent(evt){
if(allOk) {
def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug "button: $buttonNumber, value: $value"
def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
if(recentEvents.size <= 1){
switch(buttonNumber) {
case ~/.*1.*/:
executeHandlers(1, value)
break
case ~/.*2.*/:
executeHandlers(2, value)
break
case ~/.*3.*/:
executeHandlers(3, value)
break
case ~/.*4.*/:
executeHandlers(4, value)
break
}
} else {
log.debug "Found recent button press events for $buttonNumber with value $value"
}
}
}
def executeHandlers(buttonNumber, value) {
log.debug "executeHandlers: $buttonNumber - $value"
def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights)
def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks)
def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos)
def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode)
def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase)
def textMessage = findMsg('textMessage', buttonNumber)
def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens)
}
def find(type, buttonNumber, value) {
def preferenceName = type + "_" + buttonNumber + "_" + value
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def findMsg(type, buttonNumber) {
def preferenceName = type + "_" + buttonNumber
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def toggle(devices) {
log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
if (devices*.currentValue('switch').contains('on')) {
devices.off()
}
else if (devices*.currentValue('switch').contains('off')) {
devices.on()
}
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}
else {
devices.on()
}
}
def changeMode(mode) {
log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
if (location.mode != mode && location.modes?.find { it.name == mode }) {
setLocationMode(mode)
}
}
// execution filter methods
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting, location.timeZone).time
def stop = timeToday(ending, location.timeZone).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}
private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}