Merge pull request #516 from Yaima/master

Ecobee 3 - https://smartthings.atlassian.net/browse/DEVC-285
This commit is contained in:
Yaima
2016-02-16 14:52:55 -08:00
3 changed files with 313 additions and 269 deletions

View File

@@ -22,10 +22,6 @@ metadata {
capability "Polling" capability "Polling"
} }
simulator {
// TODO: define status and reply messages here
}
tiles { tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) { valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F", state("temperature", label:'${currentValue}°', unit:"F",
@@ -56,16 +52,12 @@ metadata {
} }
def refresh() { def refresh() {
log.debug "refresh..." log.debug "refresh called"
poll() poll()
} }
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChildren(this) parent.pollChild(this)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
} }

View File

@@ -19,34 +19,39 @@ metadata {
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") { definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Thermostat" capability "Thermostat"
capability "Temperature Measurement"
capability "Polling" capability "Polling"
capability "Sensor" capability "Sensor"
capability "Refresh" capability "Refresh"
capability "Relative Humidity Measurement"
command "generateEvent" command "generateEvent"
command "raiseSetpoint" command "raiseSetpoint"
command "lowerSetpoint" command "lowerSetpoint"
command "resumeProgram" command "resumeProgram"
command "switchMode" command "switchMode"
attribute "thermostatSetpoint","number" attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string" attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number"
} }
simulator { } tiles {
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) { valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F", state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[ backgroundColors:[
[value: 31, color: "#153591"], [value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"], [value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"], [value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"], [value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"], [value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"], [value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"] [value: 96, color: "#bc2323"]
] ]
) )
} }
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
@@ -94,8 +99,11 @@ metadata {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send" state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}% humidity'
}
main "temperature" main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"]) details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh", "humidity"])
} }
preferences { preferences {
@@ -107,8 +115,6 @@ metadata {
// parse events into attributes // parse events into attributes
def parse(String description) { def parse(String description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
// TODO: handle '' attribute
} }
def refresh() { def refresh() {
@@ -133,16 +139,24 @@ def generateEvent(Map results) {
def isChange = false def isChange = false
def isDisplayed = true def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText), def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name] handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") { if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
isChange = isTemperatureStateChange(device, name, value.toString()) isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed] event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString()) isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false] event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else { } else {
isChange = isStateChange(device, name, value.toString()) isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
@@ -158,13 +172,19 @@ def generateEvent(Map results) {
//return descriptionText to be shown on mobile activity feed //return descriptionText to be shown on mobile activity feed
private getThermostatDescriptionText(name, value, linkText) { private getThermostatDescriptionText(name, value, linkText) {
if(name == "temperature") { if(name == "temperature") {
return "$linkText temperature is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "$linkText temperature is $sendValue ${location.temperatureScale}"
} else if(name == "heatingSetpoint") { } else if(name == "heatingSetpoint") {
return "heating setpoint is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "heating setpoint is $sendValue ${location.temperatureScale}"
} else if(name == "coolingSetpoint"){ } else if(name == "coolingSetpoint"){
return "cooling setpoint is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "cooling setpoint is $sendValue ${location.temperatureScale}"
} else if (name == "thermostatMode") { } else if (name == "thermostatMode") {
return "thermostat mode is ${value}" return "thermostat mode is ${value}"
@@ -172,26 +192,26 @@ private getThermostatDescriptionText(name, value, linkText) {
} else if (name == "thermostatFanMode") { } else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}" return "thermostat fan mode is ${value}"
} else if (name == "humidity") {
return "humidity is ${value} %"
} else { } else {
return "${name} = ${value}" return "${name} = ${value}"
} }
} }
void setHeatingSetpoint(setpoint) { void setHeatingSetpoint(setpoint) {
setHeatingSetpoint(setpoint.toDouble()) log.debug "***heating setpoint $setpoint"
} def heatingSetpoint = setpoint.toDouble()
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble() def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint").toDouble()
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint").toDouble()
//enforce limits of heatingSetpoint //enforce limits of heatingSetpoint
if (heatingSetpoint > 79) { if (heatingSetpoint > maxHeatingSetpoint) {
heatingSetpoint = 79 heatingSetpoint = maxHeatingSetpoint
} else if (heatingSetpoint < 45) { } else if (heatingSetpoint < minHeatingSetpoint) {
heatingSetpoint = 45 heatingSetpoint = minHeatingSetpoint
} }
//enforce limits of heatingSetpoint vs coolingSetpoint //enforce limits of heatingSetpoint vs coolingSetpoint
@@ -201,32 +221,34 @@ void setHeatingSetpoint(Double setpoint) {
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} else { } else {
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app log.error "Error setHeatingSetpoint(setpoint)"
} }
} }
void setCoolingSetpoint(setpoint) { void setCoolingSetpoint(setpoint) {
setCoolingSetpoint(setpoint.toDouble()) log.debug "***cooling setpoint $setpoint"
}
void setCoolingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble() def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
def coolingSetpoint = setpoint def coolingSetpoint = setpoint.toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint").toDouble()
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint").toDouble()
if (coolingSetpoint > 92) {
coolingSetpoint = 92 if (coolingSetpoint > maxCoolingSetpoint) {
} else if (coolingSetpoint < 65) { coolingSetpoint = maxCoolingSetpoint
coolingSetpoint = 65 } else if (coolingSetpoint < minCoolingSetpoint) {
coolingSetpoint = minCoolingSetpoint
} }
//enforce limits of heatingSetpoint vs coolingSetpoint //enforce limits of heatingSetpoint vs coolingSetpoint
@@ -236,15 +258,18 @@ void setCoolingSetpoint(Double setpoint) {
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}" log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} else { } else {
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app log.error "Error setCoolingSetpoint(setpoint)"
} }
} }
@@ -448,25 +473,21 @@ def auto() {
def fanOn() { def fanOn() {
log.debug "fanOn" log.debug "fanOn"
// parent.setFanMode (this,"on") // parent.setFanMode (this,"on")
} }
def fanAuto() { def fanAuto() {
log.debug "fanAuto" log.debug "fanAuto"
// parent.setFanMode (this,"auto") // parent.setFanMode (this,"auto")
} }
def fanCirculate() { def fanCirculate() {
log.debug "fanCirculate" log.debug "fanCirculate"
// parent.setFanMode (this,"circulate") // parent.setFanMode (this,"circulate")
} }
def fanOff() { def fanOff() {
log.debug "fanOff" log.debug "fanOff"
// parent.setFanMode (this,"off") // parent.setFanMode (this,"off")
} }
def generateSetpointEvent() { def generateSetpointEvent() {
@@ -476,20 +497,41 @@ def generateSetpointEvent() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}" log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
log.debug "Heating Setpoint = ${heatingSetpoint}" log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
log.debug "Cooling Setpoint = ${coolingSetpoint}" log.debug "Cooling Setpoint = ${coolingSetpoint}"
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if(location.temperatureScale == "C")
{
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
minHeatingSetpoint = roundC(minHeatingSetpoint)
minCoolingSetpoint = roundC(minCoolingSetpoint)
heatingSetpoint = roundC(heatingSetpoint)
coolingSetpoint = roundC(coolingSetpoint)
}
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") { if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
} }
else if (mode == "cool") { else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -499,9 +541,9 @@ def generateSetpointEvent() {
sendEvent("name":"thermostatSetpoint", "value":"Off") sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "emergencyHeat") { } else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
} }
@@ -510,26 +552,30 @@ def generateSetpointEvent() {
void raiseSetpoint() { void raiseSetpoint() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def targetvalue
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
if (mode == "off" || mode == "auto") { if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint" log.warn "this mode: $mode does not allow raiseSetpoint"
} else { } else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger() def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) { if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else { } else {
targetvalue = 0 targetvalue = 0
} }
targetvalue = targetvalue + 1 targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
if (mode == "heat" && targetvalue > 79) { if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
targetvalue = 79 targetvalue = maxHeatingSetpoint
} else if (mode == "cool" && targetvalue > 92) { } else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
targetvalue = 92 targetvalue = maxCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -543,25 +589,29 @@ void raiseSetpoint() {
void lowerSetpoint() { void lowerSetpoint() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def targetvalue
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (mode == "off" || mode == "auto") { if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint" log.warn "this mode: $mode does not allow lowerSetpoint"
} else { } else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger() def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) { if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else { } else {
targetvalue = 0 targetvalue = 0
} }
targetvalue = targetvalue - 1 targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
if (mode == "heat" && targetvalue.toInteger() < 45) { if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
targetvalue = 45 targetvalue = minHeatingSetpoint
} else if (mode == "cool" && targetvalue.toInteger() < 65) { } else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
targetvalue = 65 targetvalue = minCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -575,15 +625,15 @@ void lowerSetpoint() {
void alterSetpoint(temp) { void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint def targetHeatingSetpoint
def targetCoolingSetpoint def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud //step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat"){ if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){ if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value targetCoolingSetpoint = temp.value
@@ -602,19 +652,22 @@ void alterSetpoint(temp) {
} }
} }
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " + log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}" "coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
//step2: call parent.setHold to send http request to 3rd party cloud
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) { def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false) def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint) sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint) sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}" log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else { } else {
log.error "Error alterSetpoint()" log.error "Error alterSetpoint()"
if (mode == "heat"){ if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") { } else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false) sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
@@ -626,9 +679,9 @@ void alterSetpoint(temp) {
def generateStatusEvent() { def generateStatusEvent() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature").toInteger() def temperature = device.currentValue("temperature")
def statusText def statusText
@@ -643,14 +696,14 @@ def generateStatusEvent() {
if (temperature >= heatingSetpoint) if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Heating to ${heatingSetpoint}° F" statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
} else if (mode == "cool") { } else if (mode == "cool") {
if (temperature <= coolingSetpoint) if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Cooling to ${coolingSetpoint}° F" statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -660,7 +713,7 @@ def generateStatusEvent() {
statusText = "Right Now: Off" statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") { } else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat" statusText = "Emergency Heat"
@@ -673,7 +726,18 @@ def generateStatusEvent() {
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true) sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
} }
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) { def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true) sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
} }
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -28,7 +28,7 @@ definition(
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
singleInstance: true singleInstance: true
) { ) {
appSetting "clientId" appSetting "clientId"
} }
@@ -61,7 +61,7 @@ def authPage() {
description = "Click to enter Ecobee Credentials" description = "Click to enter Ecobee Credentials"
} }
def redirectUrl = buildRedirectUrl //"${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}" def redirectUrl = buildRedirectUrl
log.debug "RedirectUrl = ${redirectUrl}" log.debug "RedirectUrl = ${redirectUrl}"
// get rid of next button until the user is actually auth'd // get rid of next button until the user is actually auth'd
if (!oauthTokenProvided) { if (!oauthTokenProvided) {
@@ -103,7 +103,7 @@ def oauthInitUrl() {
scope: "smartRead,smartWrite", scope: "smartRead,smartWrite",
client_id: smartThingsClientId, client_id: smartThingsClientId,
state: atomicState.oauthInitState, state: atomicState.oauthInitState,
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback" redirect_uri: callbackUrl
] ]
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}") redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
@@ -115,14 +115,13 @@ def callback() {
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
//verify oauthState == atomicState.oauthInitState, so the callback corresponds to the authentication request
if (oauthState == atomicState.oauthInitState){ if (oauthState == atomicState.oauthInitState){
def tokenParams = [ def tokenParams = [
grant_type: "authorization_code", grant_type: "authorization_code",
code : code, code : code,
client_id : smartThingsClientId, client_id : smartThingsClientId,
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback" redirect_uri: callbackUrl
] ]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}" def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -247,32 +246,32 @@ def getEcobeeThermostats() {
] ]
def stats = [:] def stats = [:]
try { try {
httpGet(deviceListParams) { resp -> httpGet(deviceListParams) { resp ->
if (resp.status == 200) { if (resp.status == 200) {
resp.data.thermostatList.each { stat -> resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors atomicState.remoteSensors = stat.remoteSensors
def dni = [app.id, stat.identifier].join('.') def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat) stats[dni] = getThermostatDisplayName(stat)
} }
} else { } else {
log.debug "http status: ${resp.status}" log.debug "http status: ${resp.status}"
//refresh the auth token //refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) { if (resp.data.status.code == 14) {
log.debug "Storing the failed action to try later" log.debug "Storing the failed action to try later"
atomicState.action = "getEcobeeThermostats" atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken()
} else { } else {
log.error "Authentication error, invalid authentication method, lack of credentials, etc." log.error "Authentication error, invalid authentication method, lack of credentials, etc."
} }
} }
} }
} catch(Exception e) { } catch(Exception e) {
log.debug "___exception getEcobeeThermostats(): " + e log.debug "___exception getEcobeeThermostats(): " + e
refreshAuthToken() refreshAuthToken()
} }
atomicState.thermostats = stats atomicState.thermostats = stats
return stats return stats
} }
@@ -317,7 +316,7 @@ def initialize() {
def devices = thermostats.collect { dni -> def devices = thermostats.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"Ecobee Thermostat:${atomicState.thermostats[dni]}"]) d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"${atomicState.thermostats[dni]}" ?: "Ecobee Thermostat"])
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
@@ -328,7 +327,7 @@ def initialize() {
def sensors = ecobeesensors.collect { dni -> def sensors = ecobeesensors.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"Ecobee Sensor:${atomicState.sensors[dni]}"]) d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"${atomicState.sensors[dni]}" ?:"Ecobee Sensor"])
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
@@ -354,21 +353,17 @@ def initialize() {
atomicState.thermostatData = [:] //reset Map to store thermostat data atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected //send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings" def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
state.timeSendPush = null atomicState.timeSendPush = null
atomicState.reAttempt = 0
pollHandler() //first time polling data data from thermostat pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins //automatically update devices status every 5 mins
runEvery5Minutes("poll") runEvery5Minutes("poll")
//since access_token expires every 2 hours
runEvery1Hour("refreshAuthToken")
atomicState.reAttempt = 0
} }
def pollHandler() { def pollHandler() {
@@ -389,18 +384,10 @@ def pollHandler() {
def pollChildren(child = null) { def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString() def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString" log.debug "polling children: $thermostatIdsString"
def data = ""
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
def result = false def result = false
// // TODO: test this:
//
// def jsonRequestBody = toJson([
// selection:[
// selectionType: "thermostats",
// selectionMatch: getChildDeviceIdsString(),
// includeRuntime: true
// ]
// ])
def pollParams = [ def pollParams = [
uri: apiEndpoint, uri: apiEndpoint,
@@ -411,11 +398,6 @@ def pollChildren(child = null) {
try{ try{
httpGet(pollParams) { resp -> httpGet(pollParams) { resp ->
// if (resp.data) {
// debugEventFromParent(child, "pollChildren(child) >> resp.status = ${resp.status}, resp.data = ${resp.data}")
// }
if(resp.status == 200) { if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}" log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
@@ -426,20 +408,41 @@ def pollChildren(child = null) {
log.debug "updating dni $dni" log.debug "updating dni $dni"
def data = [ data = [
coolMode: (stat.settings.coolStages > 0), coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0), heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled, autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler), auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: stat.runtime.actualTemperature / 10, temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10, heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10, coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity
] ]
data["temperature"] = data["temperature"] ? data["temperature"].toDouble().toInteger() : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? data["heatingSetpoint"].toDouble().toInteger() : data["heatingSetpoint"] if (location.temperatureScale == "F")
data["coolingSetpoint"] = data["coolingSetpoint"] ? data["coolingSetpoint"].toDouble().toInteger() : data["coolingSetpoint"] {
// debugEventFromParent(child, "Event Data = ${data}") data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data] collector[dni] = [data:data]
return collector return collector
@@ -450,9 +453,8 @@ def pollChildren(child = null) {
log.error "polling children & got http status ${resp.status}" log.error "polling children & got http status ${resp.status}"
//refresh the auth token //refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) { if (resp.data.status.code == 14) {
log.debug "Storing the failed action to try later" atomicState.action = "pollChildren"
atomicState.action = "pollChildren";
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken()
} }
@@ -463,7 +465,6 @@ def pollChildren(child = null) {
} }
} catch(Exception e) { } catch(Exception e) {
log.debug "___exception polling children: " + e log.debug "___exception polling children: " + e
// debugEventFromParent(child, "___exception polling children: " + e)
refreshAuthToken() refreshAuthToken()
} }
return result return result
@@ -476,18 +477,14 @@ def pollChild(child){
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) { if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId] def tData = atomicState.thermostats[child.device.deviceNetworkId]
// debugEventFromParent(child, "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}") //TODO comment
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent child.generateEvent(tData.data) //parse received message from parent
// return tData.data
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) { } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
// debugEventFromParent(child, "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling") //TODO comment
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null return null
} }
} }
} else { } else {
// debugEventFromParent(child, "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling") //TODO comment
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling" log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
return null return null
} }
@@ -513,9 +510,6 @@ def availableModes(child) {
{ {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null return null
} }
@@ -542,8 +536,6 @@ def currentMode(child) {
if(!tData) { if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null return null
} }
@@ -561,8 +553,12 @@ def updateSensorData() {
def occupancy = "" def occupancy = ""
it.capability.each { it.capability.each {
if (it.type == "temperature") { if (it.type == "temperature") {
temperature = it.value as Double if (location.temperatureScale == "F") {
temperature = (temperature / 10).toInteger() temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {
if(it.value == "true") if(it.value == "true")
occupancy = "active" occupancy = "active"
@@ -575,7 +571,6 @@ def updateSensorData() {
if(d) { if(d) {
d.sendEvent(name:"temperature", value: temperature) d.sendEvent(name:"temperature", value: temperature)
d.sendEvent(name:"motion", value: occupancy) d.sendEvent(name:"motion", value: occupancy)
// debugEventFromParent(d, "temperature : ${temperature}, motion:${occupancy}")
} }
} }
} }
@@ -595,64 +590,63 @@ def toQueryString(Map m) {
} }
private refreshAuthToken() { private refreshAuthToken() {
log.debug "refreshing auth token" log.debug "refreshing auth token"
if(!atomicState.refreshToken) { if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored" log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else { } else {
def refreshParams = [ def refreshParams = [
method: 'POST', method: 'POST',
uri : apiEndpoint, uri : apiEndpoint,
path : "/token", path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId], query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
] ]
log.debug refreshParams log.debug refreshParams
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials." def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost //changed to httpPost
try { try {
def jsonMap def jsonMap
httpPost(refreshParams) { resp -> httpPost(refreshParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!" log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!") debugEvent("Token refreshed ... calling saved RestAction now!")
log.debug resp log.debug resp
jsonMap = resp.data jsonMap = resp.data
if(resp.data) { if(resp.data) {
log.debug resp.data log.debug resp.data
debugEvent("Response = ${resp.data}") debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}") debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}") debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") { if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}" log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"() "${atomicState.action}"()
//remove saved action atomicState.action = ""
atomicState.action = "" }
}
} }
atomicState.action = "" atomicState.action = ""
} else { } else {
log.debug "refresh failed ${resp.status} : ${resp.status.code}" log.debug "refresh failed ${resp.status} : ${resp.status.code}"
} }
} }
} catch(Exception e) { } catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc. if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken") runIn(reAttemptPeriod, "refreshAuthToken")
@@ -665,20 +659,16 @@ private refreshAuthToken() {
sendPushAndFeeds(notificationMessage) sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0 atomicState.reAttempt = 0
} }
} }
} }
} }
} }
def resumeProgram(child, deviceId) { def resumeProgram(child, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "resumeProgram children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
//, { "type": "sendMessage", "params": { "text": "Setpoint Updated" } }
def result = sendJson(jsonRequestBody) def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "resumeProgram(child) with result ${result}")
return result return result
} }
@@ -687,27 +677,16 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10 int h = heating * 10
int c = cooling * 10 int c = cooling * 10
// log.debug "setpoints____________ - h: $heating - $h, c: $cooling - $c"
// def thermostatIdsString = getChildDeviceIdsString()
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
// def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}, { "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": "indefinite" } } ]}'
def result = sendJson(child, jsonRequestBody) def result = sendJson(child, jsonRequestBody)
// debugEventFromParent(child, "setHold: heating: ${h}, cooling: ${c} with result ${result}")
return result return result
} }
def setMode(child, mode, deviceId) { def setMode(child, mode, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "setCoolingSetpoint children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
// log.debug "Mode Request Body = ${jsonRequestBody}"
// debugEvent ("Mode Request Body = ${jsonRequestBody}")
def result = sendJson(jsonRequestBody) def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "setMode to ${mode} with result ${result}")
return result return result
} }
@@ -724,8 +703,6 @@ def sendJson(child = null, String jsonBody) {
try{ try{
httpPost(cmdParams) { resp -> httpPost(cmdParams) { resp ->
// debugEventFromParent(child, "sendJson >> resp.status ${resp.status}, resp.data: ${resp.data}")
if(resp.status == 200) { if(resp.status == 200) {
log.debug "updated ${resp.data}" log.debug "updated ${resp.data}"
@@ -741,8 +718,7 @@ def sendJson(child = null, String jsonBody) {
debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}") debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}")
//refresh the auth token //refresh the auth token
if (resp.status == 500 && resp.status.code == 14) { if (resp.status.code == 14) {
//log.debug "Storing the failed action to try later"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
debugEvent ("Refreshing OAUTH Token") debugEvent ("Refreshing OAUTH Token")
refreshAuthToken() refreshAuthToken()
@@ -757,7 +733,7 @@ def sendJson(child = null, String jsonBody) {
} catch(Exception e) { } catch(Exception e) {
log.debug "Exception Sending Json: " + e log.debug "Exception Sending Json: " + e
debugEvent ("Exception Sending JSON: " + e) debugEvent ("Exception Sending JSON: " + e)
refreshAuthToken() refreshAuthToken()
return false return false
} }
@@ -794,25 +770,37 @@ def debugEventFromParent(child, message) {
//send both push notification and mobile activity feeds //send both push notification and mobile activity feeds
def sendPushAndFeeds(notificationMessage){ def sendPushAndFeeds(notificationMessage){
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}" log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}" log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){ if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage) sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now() atomicState.timeSendPush = now()
} }
} else { } else {
sendPush("Your Ecobee thermostat " + notificationMessage) sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now() atomicState.timeSendPush = now()
} }
atomicState.authToken = null atomicState.authToken = null
} }
def sendActivityFeeds(notificationMessage) { def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices() def devices = getChildDevices()
devices.each { child -> devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
} }
}
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
} }