Compare commits

..

1 Commits

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

View File

@@ -1,197 +0,0 @@
/**
* iHomeSmartPlug iSP5
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP5", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud in a message to the user
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP5"
sendEvent(name:"model", value:"iSP5")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: property targetpowerstate1 set to value: 1"
def propertyUpdateJSON = "[{\"key\":\"targetpowerstate1\", \"value\":\"1\"}]"
def success = parent.propertyUpdate(device.deviceNetworkId, propertyUpdateJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: property targetpowerstate1 set to value: 0"
def propertyUpdateJSON = "[{\"key\":\"targetpowerstate1\", \"value\":\"0\"}]"
def success = parent.propertyUpdate(device.deviceNetworkId, propertyUpdateJSON)
if (success){
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP6
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP6", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP6"
sendEvent(name:"model", value:"iSP6")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP6X
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP6X", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP6X"
sendEvent(name:"model", value:"iSP6X")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP8
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP8", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP8"
sendEvent(name:"model", value:"iSP8")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

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,707 +0,0 @@
/**
* iHome (Connect)
*
* Copyright 2016 EVRYTHNG LTD.
*
* 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.
*
* Last reviewed: 11.08.2017
* - Use of variable serverUrl in the URLs.
* Reviewed:20.07.2017
* - Merged content with the old version modified by SmartThings
* - Removed selection of plugs, all available plugs are imported by default
* - Added location selection
* - Added DeviceWatch-DeviceStatus event
* - Added unschedule call on initialising
* - Changed from schedule to runEvery5Minutes
* - Updated refreshThngs method to support add/delete plugs automatically when they are added/removed in iHome app
* Reviewed: 04.07.2017
* - Added support for iSP6X
* - Reimplemented the import with filtering using the new tag "Active" (removed serial and use thngId)
* Review: 20.04.2017
* - Added filter by deactive property
* - Removed duplicates by creation date
*
*/
include 'localization'
definition(
name: "iHome Control (Connect)",
namespace: "ihome_control",
author: "iHome",
description: "Control your iHome Control devices within the SmartThings app!",
category: "Convenience",
iconUrl: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
iconX2Url: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
iconX3Url: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
singleInstance: true
)
{
appSetting "clientId" //Client Id of the SmartThings App in the iHome System
appSetting "clientSecret" //Client Secret of the SmartThings app in the iHome System
appSetting "iHomeServer" //URL of the iHome API
appSetting "serverUrl" //Base URL of the server hosting the redirection URI
appSetting "evrythngServer" //URL of the EVRYTHNG API (cloud control)
}
preferences {
page(name: "iHomeAuth", content:"authenticationPage", install: false)
page(name: "iHomeConnectDevices", title: "Import your iHome devices", content:"connectPage", install:false)
}
private getVendorName() { "iHome" }
/**********************************************************************************************
*
* AUTHENTICATION
*
* This block contains all the functions needed to carry out the OAuth Authentication
*
**********************************************************************************************/
/*
* Authentication endpoints (needed for OAuth)
*/
mappings {
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
path("/oauth/callback") {action: [GET: "callback"]}
}
/*
* Authentication Page
* Implements OAuth authentication with the Authorization Code Grant flow
*/
def authenticationPage()
{
log.debug "Checking authorisation..."
//Check first if the authorisation was already done before
if(state.iHomeAccessToken == null)
{
log.debug "iHome token not found, starting authorisation request"
//Check if the internal OAuth tokens have been created already
if (!state.accessToken){
log.debug "Creating access token for the callback"
createAccessToken()
}
//Create the OAuth URL of the authorisation server
def redirectUrl = "${appSettings.serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${getApiServerUrl()}"
log.debug "Redirecting to OAuth URL initializer: ${redirectUrl}"
//Display the connect your account section, it will redirect to the OAuth Authentication Server
return dynamicPage(name: "iHomeAuth", title:"iHome Control", install:false) {
section ("") {
paragraph "Welcome! In order to connect SmartThings to your ${vendorName} devices, you need to have already set up your devices using the ${vendorName} app."
href (url:redirectUrl,
style:"embedded",
required:true,
image:"https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControl_icon.png",
title:"Connect your iHome Account",
description:""
)
}
}
}
else
{
log.debug "iHome token found. Loading connect page"
loadThngs()
return connectPage()
}
}
/*
* Authentication OAuth URL
* Creates the OAuth compliant URL to the Authorisation Server
*/
def oauthInitUrl() {
log.debug "Creating OAuth URL..."
// Generate a random ID to use as a our state value. This value will be used to verify the response we get back from the 3rd party service.
state.oauthState = UUID.randomUUID().toString()
def oauthParams = [
response_type: "code",
client_id: appSettings.clientId,
state: state.oauthState,
redirect_uri: "${appSettings.serverUrl}/oauth/callback"
]
redirect(location: "${appSettings.iHomeServer}/oauth/authorize?${toQueryString(oauthParams)}")
}
/*
* Helper class to provide feedback to the user about the authentication process
*
*/
def connectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
/* width: 440px;
padding: 40px;
/!*background: #eee;*!/*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 40px;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
.image{
width: 20%;
/*height: 70px;*/
}
</style>
</head>
<body>
<div class="container">
<img class="image" src="https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png" alt="iHome icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img class="image" src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message}
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
/*
* Handler of the OAuth redirection URI
*
*/
def callback() {
log.debug "OAuth callback received..."
//OAuth server returns a code in the URL as a parameter
state.iHomeAccessCode = params.code
log.debug "Received authorization code ${state.iHomeAccessCode}"
def oauthState = params.state
def successMessage = """
<p>Your iHome Account is now connected to SmartThings!</p>
<p>Click 'Done' in the top corner to complete the setup.</p>
"""
def errorMessage = """
<p>Your iHome Account couldn't be connected to SmartThings!</p>
<p>Click 'Done' in the top corner and try again.</p>
"""
// Validate the response from the 3rd party by making sure oauthState == state.oauthInitState as expected
if (oauthState == state.oauthState){
if (state.iHomeAccessCode == null) {
log.debug "OAuth error: Access code is not present"
connectionStatus(errorMessage)
}
else {
getAccessToken();
if (state.iHomeAccessToken){
getEVTApiKey();
if(state.evtApiKey){
connectionStatus(successMessage)
}
else{
log.debug "OAuth error: EVT API KEY could not be retrieved"
connectionStatus(errorMessage)
}
}
else {
log.debug "OAuth error: Access Token could not be retrieved"
connectionStatus(errorMessage)
}
}
}
else{
log.debug "OAuth error: initial state does not match"
connectionStatus(errorMessage)
}
}
/**
* Exchanges the authorization code for an access token
*/
def getAccessToken(){
log.debug "Getting iHome access token..."
def tokenParams = [
grant_type: "authorization_code",
code: state.iHomeAccessCode,
client_id: appSettings.clientId,
client_secret: appSettings.clientSecret,
redirect_uri: "${appSettings.serverUrl}/oauth/callback"
]
def tokenUrl = "${appSettings.iHomeServer}/oauth/token/?" + toQueryString(tokenParams)
log.debug "Invoking token URL: ${tokenUrl}"
try{
def jsonMap
httpPost(uri:tokenUrl) { resp ->
if(resp.status == 200)
{
jsonMap = resp.data
if (resp.data) {
state.iHomeRefreshToken = resp?.data?.refresh_token
state.iHomeAccessToken = resp?.data?.access_token
log.debug "Access token received ${state.iHomeAccessToken}"
}
}
}
}catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here."
}
}
def getEVTApiKey() {
log.debug "Getting api key from the cloud"
def apiKeyParams = [
uri: "${appSettings.iHomeServer}/v3/evrythng/",
headers: [
"Accept": "application/json",
"Authorization": "Bearer ${state.iHomeAccessToken}"]
]
try {
def jsonMap
httpGet(apiKeyParams)
{ resp ->
if(resp.status == 200)
{
jsonMap = resp.data
if (resp.data)
{
state.evtUserId = resp?.data?.evrythng_user_id
state.evtApiKey = resp?.data?.evrythng_api_key
log.debug "Api key received: ${state.evtUserId}/${state.evtApiKey}"
//Preload thngs after getting the api key
loadThngs()
}
}
}
}catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Maps the map to query parameters for the URL
*
*/
def toQueryString(Map m)
{
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
/**********************************************************************************************
* IMPORT
* This block contains all the functions needed to import the plugs from the cloud into SmartThings
**********************************************************************************************/
def loadThngs()
{
//Products in production account
state.product = [:]
state.product["UCfXBRHnse5Rpw7PySPYNq7b"] = "iHomeSmartPlug-iSP5"
state.product["Ugrtyq8pAFVEqGSAAptgtqkc"] = "iHomeSmartPlug-iSP6"
state.product["UXNtyNexVyRrWpAQeNHq9xad"] = "iHomeSmartPlug-iSP8"
state.product["UF4NsmAEM3PhY6wwRgehdg5n"] = "iHomeSmartPlug-iSP6X"
//Save the all the plugs in the state for later use
state.thngs = [:]
log.debug "Loading available devices..."
def thngs = getThngs()
thngs.each { thng ->
//Check that the plug is compatible with a Device Type
log.debug "Checking if ${thng.id} is a compatible Device Type"
if (state.product[thng.product])
{
thng.st_devicetype = state.product[thng.product]
state.thngs["${thng.id}"] = thng
log.info "Found compatible device ${state.thngs["${thng.id}"].name}"
}
}
}
/*
* Import thngs page
* Loads the thngs available from the user, checks that they have a DeviceType associated
* and presents a list to the user
*
*/
def connectPage()
{
return dynamicPage(name: "iHomeConnectDevices", uninstall: true, install:true) {
section(""){
input "selectedLocationId", "enum", required:false, title:"", multiple:false, options:["Default Location"], defaultValue: "Default Location", submitOnChange: true
paragraph "Devices will be added automatically from your ${vendorName} account. To add or delete devices please use the Official ${vendorName} App."
}
}
}
/*
* Gets the thngs from the cloud
* This is used as the discovery process
*/
def getThngs(){
log.debug "Getting available devices..."
def url = "${appSettings.evrythngServer}/thngs?filter=tags=Active"
try {
httpGet(uri: url, headers: ["Accept": "application/json", "Authorization": state.evtApiKey]) {response ->
if (response.status == 200) {
log.debug "GET on /thngs was succesful"
log.debug "Response to GET /thngs ${response.data}"
return response.data
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Gets a thng by id from EVRYTHNG
* Used for updates
*/
def getThng(thngId){
log.trace "Getting device information..."
def url = "${appSettings.evrythngServer}/thngs/" + thngId
try {
httpGet(uri: url, headers: ["Accept": "application/json", "Authorization": state.evtApiKey]) {response ->
if (response.status == 200) {
log.debug "GET on /thngs was succesful: ${response.data}"
def isAlive = response.data.properties["~connected"]
def d = getChildDevice(thngId)
d?.sendEvent(name: "DeviceWatch-DeviceStatus", value: isAlive? "online":"offline", displayed: false, isStateChange: true)
return response.data
}
else{
log.warn "Error! Status Code was: ${response.status}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Adds all the available devices to SmartThings
* Invoked by the lifecycle initialise
*/
def importThngs() {
def thngsToImport = []
state.thngs.each { thng ->
thngsToImport.add(thng.key)
}
log.debug "Adding all available plugs...${thngsToImport}"
//Remove unselected plugs
log.debug "Checking to delete ${state.imported}"
state.imported.each{ id ->
if(thngsToImport){
if (thngsToImport.contains(id)){
log.debug "${id} is already imported"
} else{
log.debug "Removing device not longer available: ${id}"
// Error can occur if device has already been deleted or is in-use by SmartApps. Should it be force-deleted? + deleteChildDevice(thng)
try {
deleteChildDevice(id)
} catch (Exception e) {
log.error "Error deleting device with DNI $thng: $e"
}
}
} else {
log.trace "Removing unselected device with id: ${id}"
try {
deleteChildDevice(id)
}
catch(Exception error){
log.error "Error deleting device with id -> ${id}: $error"
}
}
}
state.imported = [];
thngsToImport.each { id ->
log.debug "Importing plug with id: ${id} and serial: ${state.thngs["${id}"].identifiers.serial_num}"
def smartThing = getChildDevice(id)
if(!smartThing) {
def newSmartThing = state.thngs.find { it.key == id }
log.debug "Creating SmartThing: ${newSmartThing}"
smartThing = addChildDevice("ihome_devices",
newSmartThing.value.st_devicetype,
newSmartThing.value.id,
null,
[label:"${newSmartThing.value.name}"])
log.info "Created ${smartThing.displayName} with id ${smartThing.deviceNetworkId}"
}
else {
log.trace "${smartThing.displayName} with id ${id} already exists, skipping creation"
}
//save plug in state
state.imported.add(id);
//We need to get the current status of the plug
pollChildren(smartThing.deviceNetworkId)
}
}
/**********************************************************************************************
*
* LIFECYCLE
*
**********************************************************************************************/
def installed() {
log.debug "Application installed..."
initialize()
}
def updated() {
log.debug "Application updated..."
unsubscribe()
initialize()
}
def initialize() {
log.debug "Application initialising..."
importThngs()
unschedule()
//Refresh every five minutes for external changes in the thngs
runEvery5Minutes("refreshThngs")
}
def uninstalled() {
log.debug "Removing installed plugs..."
getChildDevices().each {
log.debug "Deleting ${it.deviceNetworkId}"
try {
deleteChildDevice(it.deviceNetworkId)
}
catch (e) {
log.warn "Error deleting device, ignoring ${e}"
}
}
}
/**********************************************************************************************
* Properties and Actions UPDATES
* This block contains the functionality to update property values and send actions in EVRYTHNG cloud
* This methods are generic based on the EVRYTHNG API specification and are invoked from
* the specific Device Type that handles the properties and action types
**********************************************************************************************/
/*
* Updates a property in EVRYTHNG
*/
def propertyUpdate(thngId, propertyUpdate){
def url = "${appSettings.evrythngServer}/thngs/${thngId}/properties"
def params = [
uri: url,
headers: [
"Authorization": state.evtApiKey
],
body: propertyUpdate
]
log.debug "Sending property update to the cloud: ${params}"
try {
httpPutJson(params) { resp ->
if (resp.status == 200) {
log.debug "Response from the cloud: ${resp}"
return true
}
else {
log.debug "Response status from the cloud not valid: ${resp}"
return false
}
}
}
catch (e) {
log.debug "Something went wrong with the property update: ${e}"
return false
}
}
/*
* Sends an action to EVRYTHNG
*/
def sendAction(actionType, actionPayload){
def url = "${appSettings.evrythngServer}/actions/${actionType}"
def params = [
uri: url,
headers: [
"Authorization": state.evtApiKey
],
body: actionPayload
]
log.debug "Sending action to the cloud: ${params}"
try {
httpPostJson(params) { resp ->
if (resp.status == 201) {
log.debug "Response from the cloud: ${resp}"
return true
}
else {
log.debug "Response status from the cloud not valid: ${resp}"
return false
}
}
}
catch (e) {
log.debug "Something went wrong with sending the action: ${e}"
return false
}
}
/**
* Handler of the refreshing of all the imported things
*/
def refreshThngs(){
log.debug "Refreshing thngs"
//loading thngs to get plugs recently added or removed
loadThngs()
//import the plugs into SmartThings
importThngs()
}
/*
* Utility function to poll for a Thng and update its properties
*/
def pollChildren(thngId){
//get plug device
def smartThing = getChildDevice(thngId)
if (smartThing){
//Get plug's latest state from the cloud
log.debug "Getting updates for ${thngId}"
def plug = getThng(thngId)
if (plug == null){
smartThing.pollError()
}
else
{
//Update name
smartThing.label = plug.name
smartThing.name = plug.name
//Update properties
smartThing.updateProperties(plug.properties)
}
}
}
/* Status messages for all types of plugs */
def getConnectedMessage(){
return "Connected"
}
def getConnectionErrorMessage(){
return "Connection error. Please try again."
}
def getPlugNotConnectedMessage(){
return "Your plug seems to be disconnected."
}

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") : ""
}