Fix Ecobee access_token doesn't get refresh after it's expired

This commit is contained in:
Warodom Khamphanchai
2015-12-02 17:29:46 -08:00
parent c5da3fe4a0
commit e6367a7832
3 changed files with 123 additions and 82 deletions

View File

@@ -64,3 +64,8 @@ void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChildren(this) parent.pollChildren(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

@@ -676,3 +676,8 @@ def generateStatusEvent() {
log.debug "Generate Status Event = ${statusText}" log.debug "Generate Status Event = ${statusText}"
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) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

@@ -10,14 +10,14 @@
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines * 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
*/ */
definition( definition(
name: "Ecobee (Connect)", name: "Ecobee (Connect)",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Connect your Ecobee thermostat to SmartThings.", description: "Connect your Ecobee thermostat to SmartThings.",
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"
} }
@@ -236,27 +236,32 @@ def getEcobeeThermostats() {
] ]
def stats = [:] def stats = [:]
httpGet(deviceListParams) { resp -> try {
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.status == 500 && 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) {
log.debug "___exception getEcobeeThermostats(): " + e
refreshAuthToken()
}
atomicState.thermostats = stats atomicState.thermostats = stats
return stats return stats
} }
@@ -338,22 +343,19 @@ 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
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
state.timeSendPush = null
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")
def uninstalled() {
log.info("Uninstalling, removing child devices...")
removeChildDevices(getChildDevices())
}
private removeChildDevices(delete) {
delete.each {
deleteChildDevice(it.deviceNetworkId)
}
} }
def pollHandler() { def pollHandler() {
@@ -579,65 +581,69 @@ 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
//changed to httpPost 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."
try { //changed to httpPost
def jsonMap try {
httpPost(refreshParams) { resp -> def jsonMap
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 //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(Exception e) {
log.debug "caught exception refreshing auth token: " + e log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
} if (e.statusCode == 401) {
} sendPushAndFeeds(notificationMessage)
}
}
}
} }
def resumeProgram(child, deviceId) { def resumeProgram(child, deviceId) {
@@ -727,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)
// debugEventFromParent(child, "Exception Sending JSON: " + e) refreshAuthToken()
return false return false
} }
@@ -761,3 +767,28 @@ def debugEvent(message, displayEvent = false) {
def debugEventFromParent(child, message) { def debugEventFromParent(child, message) {
if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)} if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)}
} }
//send both push notification and mobile activity feeds
def sendPushAndFeeds(notificationMessage){
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
} else {
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
atomicState.authToken = null
}
def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices()
devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
}
}