Compare commits

...

9 Commits

Author SHA1 Message Date
Vinay Rao
ce9ac624d0 Merge pull request #1199 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-09-06 16:41:19 -07:00
Juan Pablo Risso
f3f5cc42c9 CREX-5826 - Missing braces (#1198) 2016-09-06 17:38:45 -04:00
Vinay Rao
313fe8b734 Merge pull request #1197 from larsfinander/revert_WWST-40_staging
Revert "WWST-40 Philips Hue: Implement device watch"
2016-09-06 12:33:51 -07:00
Vinay Rao
0d693386d1 Merge pull request #1194 from jimmyjames/revert-latest-ecobee-changes
revert changes for DVCSMP-1980 and SSVD-2534 on staging
2016-09-06 12:01:30 -07:00
Lars Finander
d1aee1e874 Revert "WWST-40 Philips Hue: Implement device watch"
This reverts commit bacd335991.
2016-09-06 12:42:11 -06:00
Lars Finander
3528e7da51 Merge pull request #1195 from larsfinander/DVCSMP-2004_Philips_Hue_Nullpointer_in_bridge_staging
DVCSMP-2004 Philips Hue: Nullpointer in bridge
2016-09-06 12:40:02 -06:00
Lars Finander
5c2e06c98d DVCSMP-2004 Philips Hue: Nullpointer in bridge 2016-09-06 10:43:03 -06:00
Jim Anderson
26df619b4f revert changes for DVCSMP-1980 and SSVD-2534 on staging 2016-09-06 11:22:06 -05:00
Juan Pablo Risso
af2ea04442 CREX-5826 - 2 SMS bug fix (#1193)
options.method = 'none' & variable name update

Revert variable name
2016-09-06 12:04:26 -04:00
9 changed files with 375 additions and 527 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

@@ -16,7 +16,6 @@ metadata {
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check"
command "setAdjustedColor" command "setAdjustedColor"
command "reset" command "reset"
@@ -56,10 +55,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "parse() - $description" log.debug "parse() - $description"
@@ -171,7 +166,3 @@ def verifyPercent(percent) {
return false return false
} }
} }
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -62,7 +62,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value" log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}") results << createEvent(name: "${map.name}", value: "${map.value}")
} else { } else {
log.trace "Parsing description" log.trace "Parsing description"
def msg = parseLanMessage(description) def msg = parseLanMessage(description)
if (msg.body) { if (msg.body) {
def contentType = msg.headers["Content-Type"] def contentType = msg.headers["Content-Type"]
@@ -72,13 +72,13 @@ def parse(description) {
log.info "Bridge response: $msg.body" log.info "Bridge response: $msg.body"
} else { } else {
// Sending Bulbs List to parent" // Sending Bulbs List to parent"
if (parent.state.inBulbDiscovery) if (parent.isInBulbDiscovery())
log.info parent.bulbListHandler(device.hub.id, msg.body) log.info parent.bulbListHandler(device.hub.id, msg.body)
} }
} }
else if (contentType?.contains("xml")) { else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body) parent.hubVerification(device.hub.id, msg.body)
} }
} }
} }

View File

@@ -17,7 +17,6 @@ metadata {
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check"
command "setAdjustedColor" command "setAdjustedColor"
command "reset" command "reset"
@@ -65,10 +64,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "parse() - $description" log.debug "parse() - $description"
@@ -187,7 +182,3 @@ def verifyPercent(percent) {
return false return false
} }
} }
def ping() {
log.trace "${parent.ping(this)}"
}

View File

@@ -14,7 +14,6 @@ metadata {
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check"
command "refresh" command "refresh"
} }
@@ -49,10 +48,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "parse() - $description" log.debug "parse() - $description"
@@ -92,7 +87,3 @@ void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -15,7 +15,6 @@ metadata {
capability "Color Temperature" capability "Color Temperature"
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Health Check"
command "refresh" command "refresh"
} }
@@ -54,10 +53,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "parse() - $description" log.debug "parse() - $description"
@@ -106,7 +101,3 @@ void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }
def ping() {
log.debug "${parent.ping(this)}"
}

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()
}

View File

@@ -95,7 +95,8 @@ def bridgeDiscoveryFailed() {
} }
} }
def bridgeLinking() { def bridgeLinking()
{
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
state.linkRefreshcount = linkRefreshcount + 1 state.linkRefreshcount = linkRefreshcount + 1
def refreshInterval = 3 def refreshInterval = 3
@@ -327,7 +328,7 @@ def bulbListHandler(hub, data = "") {
def object = new groovy.json.JsonSlurper().parseText(data) def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v -> object.each { k,v ->
if (v instanceof Map) if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable] bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub]
} }
} }
def bridge = null def bridge = null
@@ -447,6 +448,7 @@ def addBridge() {
updateBridgeStatus(childDevice) updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber) childDevice.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) { if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) { if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -647,7 +649,8 @@ def locationHandler(evt) {
} }
} }
} }
} else if (parsedEvent.headers && parsedEvent.body) { }
else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES" log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString() def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) { if (headerString?.contains("xml")) {
@@ -730,7 +733,7 @@ private void updateBridgeStatus(childDevice) {
private void checkBridgeStatus() { private void checkBridgeStatus() {
def bridges = getHueBridges() def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer) // Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 30) def time = now() - (1000 * 60 * 16)
bridges.each { bridges.each {
def d = getChildDevice(it.value.mac) def d = getChildDevice(it.value.mac)
if(d) { if(d) {
@@ -743,8 +746,6 @@ private void checkBridgeStatus() {
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
// set all lights to offline since bridge is not reachable
state.bulbs?.each {it.value.online = false}
} else { } else {
d.sendEvent(name: "status", value: "Online")//setOnline(false) d.sendEvent(name: "status", value: "Online")//setOnline(false)
} }
@@ -757,6 +758,10 @@ def isValidSource(macAddress) {
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
} }
def isInBulbDiscovery() {
return state.inBulbDiscovery
}
///////////////////////////////////// /////////////////////////////////////
//CHILD DEVICE METHODS //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
@@ -780,7 +785,8 @@ def parse(childDevice, description) {
if (body instanceof java.util.Map) { if (body instanceof java.util.Map) {
// get (poll) reponse // get (poll) reponse
return handlePoll(body) return handlePoll(body)
} else { }
else {
//put response //put response
return handleCommandResponse(body) return handleCommandResponse(body)
} }
@@ -877,40 +883,36 @@ private handleCommandResponse(body) {
// scan entire response before sending events to make sure they are always in the same order // scan entire response before sending events to make sure they are always in the same order
def updates = [:] def updates = [:]
body.each { payload -> body.each { payload ->
log.debug $payload log.debug $payload
if (payload?.success) { if (payload?.success) {
def childDeviceNetworkId = app.id + "/" def childDeviceNetworkId = app.id + "/"
def eventType def eventType
payload.success.each { k, v -> payload.success.each { k, v ->
def data = k.split("/") def data = k.split("/")
if (data.length == 5) { if (data.length == 5) {
childDeviceNetworkId = app.id + "/" + k.split("/")[2] childDeviceNetworkId = app.id + "/" + k.split("/")[2]
if (!updates[childDeviceNetworkId]) if (!updates[childDeviceNetworkId])
updates[childDeviceNetworkId] = [:] updates[childDeviceNetworkId] = [:]
eventType = k.split("/")[4] eventType = k.split("/")[4]
updates[childDeviceNetworkId]."$eventType" = v updates[childDeviceNetworkId]."$eventType" = v
} }
} }
} else if (payload.error) { } else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}" log.warn "Error returned from Hue bridge error = ${body?.error}"
} }
} }
// send events for each update found above (order of events should be same as handlePoll()) // send events for each update found above (order of events should be same as handlePoll())
updates.each { childDeviceNetworkId, params -> updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId) def device = getChildDevice(childDeviceNetworkId)
def id = getId(device) sendBasicEvents(device, "on", params.on)
// If device is offline, then don't send events which will update device watch sendBasicEvents(device, "bri", params.bri)
if (isOnline(id)) { sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
sendBasicEvents(device, "on", params.on) }
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
}
return [] return []
} }
/** /**
* Handles a response to a poll (GET) sent to the Hue Bridge. * Handles a response to a poll (GET) sent to the Hue Bridge.
@@ -930,32 +932,26 @@ private handleCommandResponse(body) {
* @return empty array * @return empty array
*/ */
private handlePoll(body) { private handlePoll(body) {
if (state.updating) {
// If user just executed commands, then ignore poll to not confuse the turning on/off state
return []
}
def bulbs = getChildDevices() def bulbs = getChildDevices()
for (bulb in body) { for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (device) { if (device) {
if (bulb.value.state?.reachable) { if (bulb.value.state?.reachable) {
if (state.bulbs[bulb.key]?.online == false) { sendBasicEvents(device, "on", bulb.value?.state?.on)
// light just came back online, notify device watch sendBasicEvents(device, "bri", bulb.value?.state?.bri)
def lastActivity = now() sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
}
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
} else { } else {
state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge" log.warn "$device is not reachable by Hue bridge"
} }
} }
}
return []
} }
return []
}
private updateInProgress() { private updateInProgress() {
state.updating = true state.updating = true
@@ -984,34 +980,22 @@ def hubVerification(bodytext) {
def on(childDevice) { def on(childDevice) {
log.debug "Executing 'on'" log.debug "Executing 'on'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true]) put("lights/${getId(childDevice)}/state", [on: true])
return "Bulb is turning On" return "Bulb is turning On"
} }
def off(childDevice) { def off(childDevice) {
log.debug "Executing 'off'" log.debug "Executing 'off'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "off") createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false]) put("lights/${getId(childDevice)}/state", [on: false])
return "Bulb is turning Off" return "Bulb is turning Off"
} }
def setLevel(childDevice, percent) { def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 1 - 254 // 1 - 254
def level def level
@@ -1026,64 +1010,48 @@ def setLevel(childDevice, percent) {
// that means that the light will still be on when on is called next time // that means that the light will still be on when on is called next time
// Lets emulate that here // Lets emulate that here
if (percent > 0) { if (percent > 0) {
put("lights/$id/state", [bri: level, on: true]) put("lights/${getId(childDevice)}/state", [bri: level, on: true])
} else { } else {
put("lights/$id/state", [on: false]) put("lights/${getId(childDevice)}/state", [on: false])
} }
return "Setting level to $percent" return "Setting level to $percent"
} }
def setSaturation(childDevice, percent) { def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'" log.debug "Executing 'setSaturation($percent)'"
def id = getId(childDevice) updateInProgress()
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 254 // 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254) def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/$id/state", [sat: level, on: true]) put("lights/${getId(childDevice)}/state", [sat: level, on: true])
return "Setting saturation to $percent" return "Setting saturation to $percent"
} }
def setHue(childDevice, percent) { def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'" log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 0 - 65535 // 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535) def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/$id/state", [hue: level, on: true]) put("lights/${getId(childDevice)}/state", [hue: level, on: true])
return "Setting hue to $percent" return "Setting hue to $percent"
} }
def setColorTemperature(childDevice, huesettings) { def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'" log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 153 (6500K) to 500 (2000K) // 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/$id/state", [ct: ct, on: true]) put("lights/${getId(childDevice)}/state", [ct: ct, on: true])
return "Setting color temperature to $percent" return "Setting color temperature to $percent"
} }
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
def value = [:] def value = [:]
@@ -1140,23 +1108,15 @@ def setColor(childDevice, huesettings) {
value.on = false value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off") createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value) put("lights/${getId(childDevice)}/state", value)
return "Setting color to $value" return "Setting color to $value"
} }
def ping(childDevice) {
if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) { private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) { if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1] return childDevice.device?.deviceNetworkId[3..-1]
} else { }
else {
return childDevice.device?.deviceNetworkId.split("/")[-1] return childDevice.device?.deviceNetworkId.split("/")[-1]
} }
} }
@@ -1167,11 +1127,8 @@ private poll() {
log.debug "GET: $host$uri" log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host} HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue))
}
private isOnline(id) { """, physicalgraph.device.Protocol.LAN, selectedHue))
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
} }
private put(path, body) { private put(path, body) {
@@ -1241,7 +1198,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) { if (state.bulbs instanceof java.util.List) {
def map = [:] def map = [:]
state.bulbs.unique {it.id}.each { bulb -> state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]] map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]]
} }
state.bulbs = map state.bulbs = map
} }

View File

@@ -111,16 +111,23 @@ private sendMessage(evt) {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options) sendNotificationToContacts(msg, recipients, options)
} else { } else {
if (pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
sendNotification(msg, options)
}
if (phone) { if (phone) {
options.phone = phone options.phone = phone
log.debug 'sending SMS' if (pushAndPhone != 'No') {
sendNotification(msg, options) log.debug 'Sending push and SMS'
options.method = 'both'
} else {
log.debug 'Sending SMS'
options.method = 'phone'
}
} else if (pushAndPhone != 'No') {
log.debug 'Sending push'
options.method = 'push'
} else {
log.debug 'Sending nothing'
options.method = 'none'
} }
sendNotification(msg, options)
} }
if (frequency) { if (frequency) {
state[evt.deviceId] = now() state[evt.deviceId] = now()