Merge pull request #1194 from jimmyjames/revert-latest-ecobee-changes

revert changes for DVCSMP-1980 and SSVD-2534 on staging
This commit is contained in:
Vinay Rao
2016-09-06 12:01:30 -07:00
committed by GitHub
2 changed files with 306 additions and 386 deletions

View File

@@ -152,11 +152,11 @@ def generateEvent(Map results) {
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue 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, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed] event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") { } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //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 sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, unit: temperatureScale, displayed: false] event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ } 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]
@@ -234,9 +234,9 @@ void setHeatingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint 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(heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) 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()
@@ -271,9 +271,9 @@ void setCoolingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint 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(heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) 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()
@@ -287,14 +287,14 @@ void resumeProgram() {
log.debug "resumeProgram() is called" log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.resumeProgram(deviceId)) { if (parent.resumeProgram(this, deviceId)) {
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
runIn(5, "poll") runIn(5, "poll")
log.debug "resumeProgram() is done" log.debug "resumeProgram() is done"
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true) sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
} else { } else {
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
log.error "Error resumeProgram() check parent.resumeProgram(deviceId)" log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
} }
} }
@@ -406,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
def off() { def off() {
log.debug "off" log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("off", deviceId)) if (parent.setMode (this,"off", deviceId))
generateModeEvent("off") generateModeEvent("off")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -420,7 +420,7 @@ def off() {
def heat() { def heat() {
log.debug "heat" log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("heat", deviceId)) if (parent.setMode (this,"heat", deviceId))
generateModeEvent("heat") generateModeEvent("heat")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -438,7 +438,7 @@ def emergencyHeat() {
def auxHeatOnly() { def auxHeatOnly() {
log.debug "auxHeatOnly" log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auxHeatOnly", deviceId)) if (parent.setMode (this,"auxHeatOnly", deviceId))
generateModeEvent("auxHeatOnly") generateModeEvent("auxHeatOnly")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -452,7 +452,7 @@ def auxHeatOnly() {
def cool() { def cool() {
log.debug "cool" log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("cool", deviceId)) if (parent.setMode (this,"cool", deviceId))
generateModeEvent("cool") generateModeEvent("cool")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -466,7 +466,7 @@ def cool() {
def auto() { def auto() {
log.debug "auto" log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auto", deviceId)) if (parent.setMode (this,"auto", deviceId))
generateModeEvent("auto") generateModeEvent("auto")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -489,7 +489,7 @@ def fanOn() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode) generateFanModeEvent(fanMode)
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -510,7 +510,7 @@ def fanAuto() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode) generateFanModeEvent(fanMode)
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -556,12 +556,12 @@ def generateSetpointEvent() {
if (mode == "heat") { if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
} }
else if (mode == "cool") { else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -573,7 +573,7 @@ def generateSetpointEvent() {
} else if (mode == "auxHeatOnly") { } else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
} }
@@ -608,7 +608,7 @@ void raiseSetpoint() {
targetvalue = maxCoolingSetpoint targetvalue = maxCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode raiseSetpoint() to $targetvalue" log.info "In mode $mode raiseSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -644,7 +644,7 @@ void lowerSetpoint() {
targetvalue = minCoolingSetpoint targetvalue = minCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode lowerSetpoint() to $targetvalue" log.info "In mode $mode lowerSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -690,10 +690,10 @@ void alterSetpoint(temp) {
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false) sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale) sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale) 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()"

View File

@@ -66,7 +66,7 @@ def authPage() {
// 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) {
return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) { return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) {
section() { section(){
paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button." paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button."
href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description
} }
@@ -76,7 +76,7 @@ def authPage() {
log.debug "thermostat list: $stats" log.debug "thermostat list: $stats"
log.debug "sensor list: ${sensorsDiscovered()}" log.debug "sensor list: ${sensorsDiscovered()}"
return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) { return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) {
section("") { section(""){
paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings." paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats]) input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
} }
@@ -84,7 +84,7 @@ def authPage() {
def options = sensorsDiscovered() ?: [] def options = sensorsDiscovered() ?: []
def numFound = options.size() ?: 0 def numFound = options.size() ?: 0
if (numFound > 0) { if (numFound > 0) {
section("") { section(""){
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings." paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options) input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
} }
@@ -115,12 +115,13 @@ def callback() {
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
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 redirect_uri: callbackUrl
] ]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}" def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -128,6 +129,9 @@ def callback() {
httpPost(uri: tokenUrl) { resp -> httpPost(uri: tokenUrl) { resp ->
atomicState.refreshToken = resp.data.refresh_token atomicState.refreshToken = resp.data.refresh_token
atomicState.authToken = resp.data.access_token atomicState.authToken = resp.data.access_token
log.debug "swapped token: $resp.data"
log.debug "atomicState.refreshToken: ${atomicState.refreshToken}"
log.debug "atomicState.authToken: ${atomicState.authToken}"
} }
if (atomicState.authToken) { if (atomicState.authToken) {
@@ -144,8 +148,8 @@ def callback() {
def success() { def success() {
def message = """ def message = """
<p>Your ecobee Account is now connected to SmartThings!</p> <p>Your ecobee Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p> <p>Click 'Done' to finish setup.</p>
""" """
connectionStatus(message) connectionStatus(message)
} }
@@ -167,63 +171,64 @@ def connectionStatus(message, redirectUrl = null) {
} }
def html = """ def html = """
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta name="viewport" content="width=640"> <meta name="viewport" content="width=640">
<title>Ecobee & SmartThings connection</title> <title>Ecobee & SmartThings connection</title>
<style type="text/css"> <style type="text/css">
@font-face { @font-face {
font-family: 'Swiss 721 W01 Thin'; 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');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'), 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.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.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg'); url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Swiss 721 W01 Light'; 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');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'), 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.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.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg'); url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
.container { .container {
width: 90%; width: 90%;
padding: 4%; padding: 4%;
text-align: center; /*background: #eee;*/
} text-align: center;
img { }
vertical-align: middle; img {
} vertical-align: middle;
p { }
font-size: 2.2em; p {
font-family: 'Swiss 721 W01 Thin'; font-size: 2.2em;
text-align: center; font-family: 'Swiss 721 W01 Thin';
color: #666666; text-align: center;
padding: 0 40px; color: #666666;
margin-bottom: 0; padding: 0 40px;
} margin-bottom: 0;
span { }
font-family: 'Swiss 721 W01 Light'; span {
} font-family: 'Swiss 721 W01 Light';
</style> }
</head> </style>
<body> </head>
<div class="container"> <body>
<div class="container">
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/ecobee%402x.png" alt="ecobee icon" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/ecobee%402x.png" alt="ecobee icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message} ${message}
</div> </div>
</body> </body>
</html> </html>
""" """
render contentType: 'text/html', data: html render contentType: 'text/html', data: html
} }
@@ -232,26 +237,19 @@ def getEcobeeThermostats() {
log.debug "getting device list" log.debug "getting device list"
atomicState.remoteSensors = [] atomicState.remoteSensors = []
def bodyParams = [ def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
selection: [
selectionType: "registered",
selectionMatch: "",
includeRuntime: true,
includeSensors: true
]
]
def deviceListParams = [ def deviceListParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
// TODO - the query string below is not consistent with the Ecobee docs: query: [format: 'json', body: requestBody]
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(bodyParams)]
] ]
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 = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
@@ -291,10 +289,9 @@ Map sensorsDiscovered() {
} }
def getThermostatDisplayName(stat) { def getThermostatDisplayName(stat) {
if(stat?.name) { if(stat?.name)
return stat.name.toString() return stat.name.toString()
} return (getThermostatTypeName(stat) + " (${stat.identifier})").toString()
return (getThermostatTypeName(stat) + " (${stat.identifier})").toString()
} }
def getThermostatTypeName(stat) { def getThermostatTypeName(stat) {
@@ -313,6 +310,7 @@ def updated() {
} }
def initialize() { def initialize() {
log.debug "initialize" log.debug "initialize"
def devices = thermostats.collect { dni -> def devices = thermostats.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
@@ -352,6 +350,8 @@ def initialize() {
log.warn "delete: ${delete}, deleting ${delete.size()} thermostats" log.warn "delete: ${delete}, deleting ${delete.size()} thermostats"
delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management) delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management)
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)
@@ -381,41 +381,75 @@ 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 requestBody = [
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
def pollParams = [ def pollParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
// TODO - the query string below is not consistent with the Ecobee docs: query: [format: 'json', body: jsonRequestBody]
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml ]
query: [format: 'json', body: toJson(requestBody)]
]
try{ try{
httpGet(pollParams) { resp -> httpGet(pollParams) { resp ->
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
updateSensorData() atomicState.thermostatData = resp.data
storeThermostatData(resp.data.thermostatList) updateSensorData()
result = true atomicState.thermostats = resp.data.thermostatList.inject([:]) { collector, stat ->
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" def dni = [ app.id, stat.identifier ].join('.')
}
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 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,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F")
{
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]
return collector
}
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
} }
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status log.trace "Exception polling children: " + e.response.data.status
@@ -429,12 +463,13 @@ def pollChildren(child = null) {
} }
// Poll Child is invoked from the Child Device itself as part of the Poll Capability // Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild() { def pollChild(){
def devices = getChildDevices() def devices = getChildDevices()
if (pollChildren()) { if (pollChildren()){
devices.each { child -> devices.each { 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]
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
@@ -457,38 +492,36 @@ void poll() {
} }
def availableModes(child) { def availableModes(child) {
debugEvent ("atomicState.thermostats = ${atomicState.thermostats}") debugEvent ("atomicState.thermostats = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}") debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId] def tData = atomicState.thermostats[child.device.deviceNetworkId]
debugEvent("Data = ${tData}") debugEvent("Data = ${tData}")
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"
return null return null
} }
def modes = ["off"] def modes = ["off"]
if (tData.data.heatMode) { if (tData.data.heatMode) modes.add("heat")
modes.add("heat") if (tData.data.coolMode) modes.add("cool")
} if (tData.data.autoMode) modes.add("auto")
if (tData.data.coolMode) { if (tData.data.auxHeatMode) modes.add("auxHeatOnly")
modes.add("cool")
} modes
if (tData.data.autoMode) {
modes.add("auto")
}
if (tData.data.auxHeatMode) {
modes.add("auxHeatOnly")
}
return modes
} }
def currentMode(child) { def currentMode(child) {
debugEvent ("atomicState.Thermos = ${atomicState.thermostats}") debugEvent ("atomicState.Thermos = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}") debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId] def tData = atomicState.thermostats[child.device.deviceNetworkId]
@@ -497,11 +530,14 @@ 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"
return null return null
} }
def mode = tData.data.thermostatMode def mode = tData.data.thermostatMode
return mode
mode
} }
def updateSensorData() { def updateSensorData() {
@@ -522,12 +558,12 @@ def updateSensorData() {
} }
} }
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {
if(it.value == "true") { if(it.value == "true")
occupancy = "active" occupancy = "active"
} else { else
occupancy = "inactive" occupancy = "inactive"
}
} }
} }
def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code
@@ -546,7 +582,7 @@ def getChildDeviceIdsString() {
} }
def toJson(Map m) { def toJson(Map m) {
return groovy.json.JsonOutput.toJson(m) return new org.json.JSONObject(m).toString()
} }
def toQueryString(Map m) { def toQueryString(Map m) {
@@ -559,24 +595,54 @@ private refreshAuthToken() {
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
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!")
saveTokenAndResumeAction(resp.data)
} log.debug resp
}
jsonMap = resp.data
if(resp.data) {
log.debug resp.data
debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"()
atomicState.action = ""
}
}
atomicState.action = ""
}
}
} catch (groovyx.net.http.HttpResponseException 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
@@ -596,220 +662,118 @@ private refreshAuthToken() {
} }
} }
/** def resumeProgram(child, deviceId) {
* Saves the refresh and auth token from the passed-in JSON object,
* and invokes any previously executing action that did not complete due to
* an expired token. def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
* def result = sendJson(jsonRequestBody)
* @param json - an object representing the parsed JSON response from Ecobee return result
*/
private void saveTokenAndResumeAction(json) {
log.debug "token response json: $json"
if (json) {
debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (atomicState.action) {
log.debug "got refresh token, executing next action: ${atomicState.action}"
"${atomicState.action}"()
}
} else {
log.warn "did not get response body from refresh token response"
}
atomicState.action = ""
} }
/** def setHold(child, heating, cooling, deviceId, sendHoldType) {
* Executes the resume program command on the Ecobee thermostat
* @param deviceId - the ID of the device int h = heating * 10
* int c = cooling * 10
* @retrun true if the command was successful, false otherwise. def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
*/
boolean resumeProgram(deviceId) { def result = sendJson(child, jsonRequestBody)
def payload = [ return result
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "resumeProgram"
]
]
]
return sendCommandToEcobee(payload)
} }
/** def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) {
* Executes the set hold command on the Ecobee thermostat
* @param heating - The heating temperature to set in fahrenheit
* @param cooling - the cooling temperature to set in fahrenheit
* @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
*
* @return true if the command was successful, false otherwise
*/
boolean setHold(heating, cooling, deviceId, sendHoldType) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
def payload = [ int h = heating * 10
selection: [ int c = cooling * 10
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType
]
]
]
]
return sendCommandToEcobee(payload)
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
} }
/** def setMode(child, mode, deviceId) {
* Executes the set fan mode command on the Ecobee thermostat def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
* @param heating - The heating temperature to set in fahrenheit
* @param cooling - the cooling temperature to set in fahrenheit
* @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
* @param fanMode - the fan mode to set to
*
* @return true if the command was successful, false otherwise
*/
boolean setFanMode(heating, cooling, deviceId, sendHoldType, fanMode) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
def payload = [ def result = sendJson(jsonRequestBody)
selection: [ return result
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType,
fan: fanMode
]
]
]
]
return sendCommandToEcobee(payload)
} }
/** def sendJson(child = null, String jsonBody) {
* Sets the mode of the Ecobee thermostat
* @param mode - the mode to set to
* @param deviceId - the ID of the device
*
* @return true if the command was successful, false otherwise
*/
boolean setMode(mode, deviceId) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
thermostat: [
settings: [
hvacMode: mode
]
]
]
return sendCommandToEcobee(payload)
}
/** def returnStatus = false
* Makes a request to the Ecobee API to actuate the thermostat.
* Used by command methods to send commands to Ecobee.
*
* @param bodyParams - a map of request parameters to send to Ecobee.
*
* @return true if the command was accepted by Ecobee without error, false otherwise.
*/
private boolean sendCommandToEcobee(Map bodyParams) {
def isSuccess = false
def cmdParams = [ def cmdParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
body: toJson(bodyParams) body: jsonBody
] ]
try{ try{
httpPost(cmdParams) { resp -> httpPost(cmdParams) { resp ->
if(resp.status == 200) {
log.debug "updated ${resp.data}" if(resp.status == 200) {
def returnStatus = resp.data.status.code
if (returnStatus == 0) { log.debug "updated ${resp.data}"
log.debug "Successful call to ecobee API." returnStatus = resp.data.status.code
isSuccess = true if (resp.data.status.code == 0)
} else { log.debug "Successful call to ecobee API."
log.debug "Error return code = ${returnStatus}" else {
debugEvent("Error return code = ${returnStatus}") log.debug "Error return code = ${resp.data.status.code}"
} debugEvent("Error return code = ${resp.data.status.code}")
} }
} }
}
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception Sending Json: " + e.response.data.status log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
// TODO - figure out why we're setting the next action to be pollChildren
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
atomicState.action = "pollChildren" atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken()
} else { }
else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.") debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc." log.error "Authentication error, invalid authentication method, lack of credentials, etc."
} }
} }
return isSuccess if (returnStatus == 0)
return true
else
return false
} }
def getChildName() { return "Ecobee Thermostat" } def getChildName() { "Ecobee Thermostat" }
def getSensorChildName() { return "Ecobee Sensor" } def getSensorChildName() { "Ecobee Sensor" }
def getServerUrl() { return "https://graph.api.smartthings.com" } def getServerUrl() { return "https://graph.api.smartthings.com" }
def getShardUrl() { return getApiServerUrl() } def getShardUrl() { return getApiServerUrl() }
def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback" } def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" }
def getApiEndpoint() { return "https://api.ecobee.com" } def getApiEndpoint() { "https://api.ecobee.com" }
def getSmartThingsClientId() { return appSettings.clientId } def getSmartThingsClientId() { appSettings.clientId }
def debugEvent(message, displayEvent = false) { def debugEvent(message, displayEvent = false) {
def results = [ def results = [
name: "appdebug", name: "appdebug",
descriptionText: message, descriptionText: message,
displayed: displayEvent displayed: displayEvent
] ]
log.debug "Generating AppDebug Event: ${results}" log.debug "Generating AppDebug Event: ${results}"
sendEvent (results) sendEvent (results)
}
def debugEventFromParent(child, message) {
if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)}
} }
//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()
@@ -822,58 +786,6 @@ def sendPushAndFeeds(notificationMessage) {
atomicState.authToken = null atomicState.authToken = null
} }
/**
* Stores data about the thermostats in atomicState.
* @param thermostats - a list of thermostats as returned from the Ecobee API
*/
private void storeThermostatData(thermostats) {
log.trace "Storing thermostat data: $thermostats"
def data
atomicState.thermostats = thermostats.inject([:]) { collector, stat ->
def dni = [ app.id, stat.identifier ].join('.')
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 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,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F") {
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]
return collector
}
log.debug "updated ${atomicState.thermostats?.size()} thermostats: ${atomicState.thermostats}"
}
def sendActivityFeeds(notificationMessage) { def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices() def devices = getChildDevices()
devices.each { child -> devices.each { child ->
@@ -881,6 +793,14 @@ def sendActivityFeeds(notificationMessage) {
} }
} }
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) { def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2) return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
} }
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}