From 6c5b93da87bca18793dc973a36aade81deb3eaa7 Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Wed, 10 Feb 2016 10:43:21 -0600 Subject: [PATCH 1/8] outlet: Added fingerprint for new ST outlet Resolves: https://smartthings.atlassian.net/browse/DVCSMP-1360 --- .../zigbee-switch-power.src/zigbee-switch-power.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy b/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy index 2c3c08f..7521aec 100644 --- a/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy +++ b/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy @@ -25,6 +25,7 @@ metadata { fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch" + fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019", manufacturer: "SmartThings", model: "outletv4", deviceJoinName: "Outlet" } tiles(scale: 2) { From 9263107f0e0fc41b2a7cdee7c951462de137c8e0 Mon Sep 17 00:00:00 2001 From: Luke Bredeson Date: Tue, 16 Feb 2016 17:05:09 -0600 Subject: [PATCH 2/8] INSIDE-787: Improve Wemo (Connect) event subscriptions --- .../wemo-connect.src/wemo-connect.groovy | 200 ++++++++++++++---- 1 file changed, 164 insertions(+), 36 deletions(-) diff --git a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy index a5819e1..0002c37 100644 --- a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy +++ b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy @@ -16,14 +16,14 @@ * Date: 2013-09-06 */ definition( - name: "Wemo (Connect)", - namespace: "smartthings", - author: "SmartThings", - description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.", - category: "SmartThings Labs", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png", - singleInstance: true + name: "Wemo (Connect)", + namespace: "smartthings", + author: "SmartThings", + description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.", + category: "SmartThings Labs", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png", + singleInstance: true ) preferences { @@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) { sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1 HOST: ${deviceNetworkId} -""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}")) +""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"])) } private verifyDevices() { @@ -52,6 +52,13 @@ private verifyDevices() { } } +void ssdpSubscribe() { + subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler) +} + def firstPage() { if(canInstallLabs()) @@ -62,7 +69,7 @@ def firstPage() log.debug "REFRESH COUNT :: ${refreshCount}" - subscribe(location, null, locationHandler, [filterEvents:false]) + ssdpSubscribe() //ssdp request every 25 seconds if((refreshCount % 5) == 0) { @@ -105,9 +112,7 @@ def devicesDiscovered() { def motions = getWemoMotions() def lightSwitches = getWemoLightSwitches() def devices = switches + motions + lightSwitches - def list = [] - - list = devices?.collect{ [app.id, it.ssdpUSN].join('.') } + devices?.collect{ [app.id, it.ssdpUSN].join('.') } } def switchesDiscovered() { @@ -175,8 +180,9 @@ def updated() { def initialize() { unsubscribe() - unschedule() - subscribe(location, null, locationHandler, [filterEvents:false]) + unschedule() + + ssdpSubscribe() if (selectedSwitches) addSwitches() @@ -189,7 +195,7 @@ def initialize() { runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds - runEvery5Minutes("refresh") + runEvery5Minutes("refresh") } def resubscribe() { @@ -199,7 +205,7 @@ def resubscribe() { def refresh() { log.debug "refresh() called" - doDeviceSync() + doDeviceSync() refreshDevices() } @@ -235,14 +241,14 @@ def addSwitches() { if (!d) { log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}" d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [ - "label": selectedSwitch?.value?.name ?: "Wemo Switch", - "data": [ - "mac": selectedSwitch.value.mac, - "ip": selectedSwitch.value.ip, - "port": selectedSwitch.value.port - ] + "label": selectedSwitch?.value?.name ?: "Wemo Switch", + "data": [ + "mac": selectedSwitch.value.mac, + "ip": selectedSwitch.value.ip, + "port": selectedSwitch.value.port + ] ]) - def ipvalue = convertHexToIP(selectedSwitch.value.ip) + def ipvalue = convertHexToIP(selectedSwitch.value.ip) d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" } else { @@ -273,9 +279,9 @@ def addMotions() { "port": selectedMotion.value.port ] ]) - def ipvalue = convertHexToIP(selectedMotion.value.ip) - d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") - log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" + def ipvalue = convertHexToIP(selectedMotion.value.ip) + d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") + log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" } else { log.debug "found ${d.displayName} with id $dni already exists" } @@ -304,26 +310,147 @@ def addLightSwitches() { "port": selectedLightSwitch.value.port ] ]) - def ipvalue = convertHexToIP(selectedLightSwitch.value.ip) - d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") + def ipvalue = convertHexToIP(selectedLightSwitch.value.ip) + d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") log.debug "created ${d.displayName} with id $dni" } else { - log.debug "found ${d.displayName} with id $dni already exists" + log.debug "found ${d.displayName} with id $dni already exists" } } } +def ssdpSwitchHandler(evt) { + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def switches = getWemoSwitches() + if (!(switches."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { + log.debug "Device was already found in state..." + def d = switches."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + log.debug "$d.ip <==> $parsedEvent.ip" + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + def child = getChildDevice(parsedEvent.mac) + child.subscribe(parsedEvent.ip, parsedEvent.port) + child.poll() + } + } +} + +def ssdpMotionHandler(evt) { + log.info("ssdpMotionHandler") + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def motions = getWemoMotions() + if (!(motions."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { // just update the values + log.debug "Device was already found in state..." + + def d = motions."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + } + + if (deviceChangedValues) { + def children = getChildDevices() + log.debug "Found children ${children}" + children.each { + if (it.getDeviceDataByName("mac") == parsedEvent.mac) { + log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}" + it.subscribe(parsedEvent.ip, parsedEvent.port) + } + } + } + } +} + +def ssdpLightSwitchHandler(evt) { + log.info("ssdpLightSwitchHandler") + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def lightSwitches = getWemoLightSwitches() + + if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { + log.debug "Device was already found in state..." + + def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + def child = getChildDevice(parsedEvent.mac) + log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" + child.subscribe(parsedEvent.ip, parsedEvent.port) + } + } +} + +void setupHandler(hubResponse) { + String contentType = hubResponse?.headers['Content-Type'] + if (contentType != null && contentType == 'text/xml') { + def body = hubResponse.xml + def wemoDevices = [] + String deviceType = body?.device?.deviceType?.text() ?: "" + if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) { + wemoDevices = getWemoSwitches() + } else if (deviceType.startsWith("urn:Belkin:device:sensor")) { + wemoDevices = getWemoMotions() + } else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) { + wemoDevices = getWemoLightSwitches() + } + + def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())} + if (wemoDevice) { + wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true] + } else { + log.error "/setup.xml returned a wemo device that didn't exist" + } + } +} + +@Deprecated def locationHandler(evt) { def description = evt.description def hub = evt?.hubId def parsedEvent = parseDiscoveryMessage(description) parsedEvent << ["hub":hub] - log.debug parsedEvent + log.debug parsedEvent if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) { def switches = getWemoSwitches() if (!(switches."${parsedEvent.ssdpUSN.toString()}")) { - //if it doesn't already exist + //if it doesn't already exist switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { log.debug "Device was already found in state..." @@ -335,16 +462,16 @@ def locationHandler(evt) { d.port = parsedEvent.port deviceChangedValues = true log.debug "Device's port or ip changed..." - def child = getChildDevice(parsedEvent.mac) + def child = getChildDevice(parsedEvent.mac) child.subscribe(parsedEvent.ip, parsedEvent.port) - child.poll() + child.poll() } } } else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) { def motions = getWemoMotions() if (!(motions."${parsedEvent.ssdpUSN.toString()}")) { - //if it doesn't already exist + //if it doesn't already exist motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { // just update the values log.debug "Device was already found in state..." @@ -459,6 +586,7 @@ def locationHandler(evt) { } } +@Deprecated private def parseXmlBody(def body) { def decodedBytes = body.decodeBase64() def bodyString @@ -540,4 +668,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) { private List getRealHubFirmwareVersions() { return location.hubs*.firmwareVersionString.findAll { it } -} +} \ No newline at end of file From 24bfb7f20fa33cff7161c78dbf5740a8da328c04 Mon Sep 17 00:00:00 2001 From: Yaima Valdivia Date: Wed, 17 Feb 2016 11:15:26 -0800 Subject: [PATCH 3/8] Ecobee fanMode available - https://smartthings.atlassian.net/browse/DVCSMP-1501 https://smartthings.atlassian.net/browse/DVCSMP-1501 --- .../ecobee-thermostat.groovy | 100 +++++++++++------- .../ecobee-connect.src/ecobee-connect.groovy | 17 ++- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 7c718b3..2ddafab 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -30,6 +30,7 @@ metadata { command "lowerSetpoint" command "resumeProgram" command "switchMode" + command "switchFanMode" attribute "thermostatSetpoint","number" attribute "thermostatStatus","string" @@ -63,10 +64,9 @@ metadata { state "updating", label:"Working", icon: "st.secondary.secondary" } standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { - state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on" - state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off" - state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate" - state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto" + state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto" + state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on" + state "updating", label:"Working", icon: "st.secondary.secondary" } standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") { state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up" @@ -96,14 +96,14 @@ metadata { state "default", action:"refresh.refresh", icon:"st.secondary.refresh" } standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") { - state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send" + state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send" state "updating", label:"Working", icon: "st.secondary.secondary" } valueTile("humidity", "device.humidity", decoration: "flat") { - state "humidity", label:'${currentValue}% humidity' + state "humidity", label:'${currentValue}%' } main "temperature" - details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh", "humidity"]) + details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","resumeProgram", "humidity", "refresh"]) } preferences { @@ -154,6 +154,9 @@ def generateEvent(Map results) { } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ isChange = isStateChange(device, name, value.toString()) event << [value: value.toString(), isStateChange: isChange, displayed: false] + } else if (name=="thermostatFanMode"){ + isChange = isStateChange(device, name, value.toString()) + event << [value: value.toString(), isStateChange: isChange, displayed: false] } else if (name=="humidity") { isChange = isStateChange(device, name, value.toString()) event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"] @@ -303,7 +306,7 @@ def modes() { } def fanModes() { - ["off", "on", "auto", "circulate"] + ["on", "auto"] } def switchMode() { @@ -332,17 +335,15 @@ def switchFanMode() { def returnCommand switch (currentFanMode) { - case "fanAuto": - returnCommand = switchToFanMode("fanOn") + case "on": + returnCommand = switchToFanMode("auto") break - case "fanOn": - returnCommand = switchToFanMode("fanCirculate") - break - case "fanCirculate": - returnCommand = switchToFanMode("fanAuto") + case "auto": + returnCommand = switchToFanMode("on") break + } - if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") } + if(!currentFanMode) { returnCommand = switchToFanMode("auto") } returnCommand } @@ -351,25 +352,20 @@ def switchToFanMode(nextMode) { log.debug "switching to fan mode: $nextMode" def returnCommand - if(nextMode == "fanAuto") { - if(!fanModes.contains("fanAuto")) { + if(nextMode == "auto") { + if(!fanModes.contains("auto")) { returnCommand = fanAuto() } else { - returnCommand = switchToFanMode("fanOn") + returnCommand = switchToFanMode("on") } - } else if(nextMode == "fanOn") { - if(!fanModes.contains("fanOn")) { + } else if(nextMode == "on") { + if(!fanModes.contains("on")) { returnCommand = fanOn() } else { - returnCommand = switchToFanMode("fanCirculate") - } - } else if(nextMode == "fanCirculate") { - if(!fanModes.contains("fanCirculate")) { - returnCommand = fanCirculate() - } else { - returnCommand = switchToFanMode("fanAuto") + returnCommand = switchToFanMode("auto") } } + returnCommand } @@ -379,13 +375,10 @@ def getDataByName(String name) { def setThermostatMode(String value) { log.debug "setThermostatMode({$value})" - } def setThermostatFanMode(String value) { - log.debug "setThermostatFanMode({$value})" - } def generateModeEvent(mode) { @@ -472,23 +465,48 @@ def auto() { def fanOn() { log.debug "fanOn" -// parent.setFanMode (this,"on") + String fanMode = "on" + def heatingSetpoint = device.currentValue("heatingSetpoint") + def coolingSetpoint = device.currentValue("coolingSetpoint") + def deviceId = device.deviceNetworkId.split(/\./).last() + + def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" + + def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint + def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint + + if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { + generateFanModeEvent(fanMode) + } else { + log.debug "Error setting new mode." + def currentFanMode = device.currentState("thermostatFanMode")?.value + generateModeEvent(currentFanMode) // reset the tile back + } } def fanAuto() { log.debug "fanAuto" -// parent.setFanMode (this,"auto") + String fanMode = "auto" + def heatingSetpoint = device.currentValue("heatingSetpoint") + def coolingSetpoint = device.currentValue("coolingSetpoint") + def deviceId = device.deviceNetworkId.split(/\./).last() + + def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" + + def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint + def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint + + if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { + generateFanModeEvent(fanMode) + } else { + log.debug "Error setting new mode." + def currentFanMode = device.currentState("thermostatFanMode")?.value + generateModeEvent(currentFanMode) // reset the tile back + } } -def fanCirculate() { - log.debug "fanCirculate" -// parent.setFanMode (this,"circulate") -} -def fanOff() { - log.debug "fanOff" -// parent.setFanMode (this,"off") -} + def generateSetpointEvent() { diff --git a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy index 11d6f55..92abc60 100644 --- a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy +++ b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy @@ -422,7 +422,8 @@ def pollChildren(child = null) { heatingSetpoint: stat.runtime.desiredHeat / 10, coolingSetpoint: stat.runtime.desiredCool / 10, thermostatMode: stat.settings.hvacMode, - humidity: stat.runtime.actualHumidity + humidity: stat.runtime.actualHumidity, + thermostatFanMode: stat.runtime.desiredFanMode ] if (location.temperatureScale == "F") @@ -676,9 +677,19 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) { int h = heating * 10 int c = cooling * 10 - - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}' + + def result = sendJson(child, jsonRequestBody) + return result +} + +def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) { + + int h = heating * 10 + int c = cooling * 10 + + + 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 } From f073df0a576213e4e308abd5cd7b8cd6055ea48c Mon Sep 17 00:00:00 2001 From: Yaima Valdivia Date: Wed, 17 Feb 2016 13:28:10 -0800 Subject: [PATCH 4/8] Fixed Ecobee - HH errors --- .../ecobee-thermostat.groovy | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 2ddafab..6af6ecb 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -204,11 +204,11 @@ private getThermostatDescriptionText(name, value, linkText) { void setHeatingSetpoint(setpoint) { log.debug "***heating setpoint $setpoint" - def heatingSetpoint = setpoint.toDouble() - def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble() + def heatingSetpoint = setpoint + def coolingSetpoint = device.currentValue("coolingSetpoint") def deviceId = device.deviceNetworkId.split(/\./).last() - def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint").toDouble() - def minHeatingSetpoint = device.currentValue("minHeatingSetpoint").toDouble() + def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") + def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") //enforce limits of heatingSetpoint if (heatingSetpoint > maxHeatingSetpoint) { @@ -241,11 +241,11 @@ void setHeatingSetpoint(setpoint) { void setCoolingSetpoint(setpoint) { log.debug "***cooling setpoint $setpoint" - def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble() - def coolingSetpoint = setpoint.toDouble() + def heatingSetpoint = device.currentValue("heatingSetpoint") + def coolingSetpoint = setpoint def deviceId = device.deviceNetworkId.split(/\./).last() - def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint").toDouble() - def minCoolingSetpoint = device.currentValue("minCoolingSetpoint").toDouble() + def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") + def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") if (coolingSetpoint > maxCoolingSetpoint) { @@ -505,9 +505,6 @@ def fanAuto() { } } - - - def generateSetpointEvent() { log.debug "Generate SetPoint Event" @@ -536,6 +533,7 @@ def generateSetpointEvent() { coolingSetpoint = roundC(coolingSetpoint) } + sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale) From 6c125fe80fd25eefd973305fcb7279d2f87a3acc Mon Sep 17 00:00:00 2001 From: Yaima Valdivia Date: Thu, 18 Feb 2016 14:49:26 -0800 Subject: [PATCH 5/8] Better exception handling of Ecobee Refreshing only if status code 14 - Authentication token has expired. Refresh your tokens. --- .../ecobee-connect.src/ecobee-connect.groovy | 83 +++++++------------ 1 file changed, 30 insertions(+), 53 deletions(-) diff --git a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy index 92abc60..0ab4aa4 100644 --- a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy +++ b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy @@ -257,21 +257,16 @@ def getEcobeeThermostats() { } } else { log.debug "http status: ${resp.status}" - //refresh the auth token - if (resp.data.status.code == 14) { - log.debug "Storing the failed action to try later" - atomicState.action = "getEcobeeThermostats" - log.debug "Refreshing your auth_token!" - refreshAuthToken() - } else { - log.error "Authentication error, invalid authentication method, lack of credentials, etc." - } } } - } catch(Exception e) { - log.debug "___exception getEcobeeThermostats(): " + e - refreshAuthToken() - } + } catch (groovyx.net.http.HttpResponseException e) { + log.trace "Exception polling children: " + e.response.data.status + if (e.response.data.status.code == 14) { + atomicState.action = "getEcobeeThermostats" + log.debug "Refreshing your auth_token!" + refreshAuthToken() + } + } atomicState.thermostats = stats return stats } @@ -450,23 +445,15 @@ def pollChildren(child = null) { } result = true log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" - } else { - log.error "polling children & got http status ${resp.status}" - - //refresh the auth token - if (resp.data.status.code == 14) { - atomicState.action = "pollChildren" - log.debug "Refreshing your auth_token!" - refreshAuthToken() - } - else { - log.error "Authentication error, invalid authentication method, lack of credentials, etc." - } } } - } catch(Exception e) { - log.debug "___exception polling children: " + e - refreshAuthToken() + } catch (groovyx.net.http.HttpResponseException e) { + log.trace "Exception polling children: " + e.response.data.status + if (e.response.data.status.code == 14) { + atomicState.action = "pollChildren" + log.debug "Refreshing your auth_token!" + refreshAuthToken() + } } return result } @@ -642,16 +629,14 @@ private refreshAuthToken() { } atomicState.action = "" - } else { - log.debug "refresh failed ${resp.status} : ${resp.status.code}" } } } catch (groovyx.net.http.HttpResponseException e) { log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" def reAttemptPeriod = 300 // in sec - if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc. + if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc. runIn(reAttemptPeriod, "refreshAuthToken") - } else if (e.statusCode == 401) { //refresh token is expired + } else if (e.statusCode == 401) { // unauthorized atomicState.reAttempt = atomicState.reAttempt + 1 log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}" if (atomicState.reAttempt <= 3) { @@ -724,29 +709,21 @@ def sendJson(child = null, String jsonBody) { log.debug "Error return code = ${resp.data.status.code}" debugEvent("Error return code = ${resp.data.status.code}") } - } else { - log.error "sent Json & got http status ${resp.status} - ${resp.status.code}" - debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}") - - //refresh the auth token - if (resp.status.code == 14) { - log.debug "Refreshing your auth_token!" - debugEvent ("Refreshing OAUTH Token") - refreshAuthToken() - return false - } else { - debugEvent ("Authentication error, invalid authentication method, lack of credentials, etc.") - log.error "Authentication error, invalid authentication method, lack of credentials, etc." - return false - } } } - } catch(Exception e) { - log.debug "Exception Sending Json: " + e - debugEvent ("Exception Sending JSON: " + e) - refreshAuthToken() - return false - } + } catch (groovyx.net.http.HttpResponseException e) { + log.trace "Exception Sending Json: " + e.response.data.status + debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") + if (e.response.data.status.code == 14) { + atomicState.action = "pollChildren" + log.debug "Refreshing your auth_token!" + refreshAuthToken() + } + else { + debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.") + log.error "Authentication error, invalid authentication method, lack of credentials, etc." + } + } if (returnStatus == 0) return true From 097584944e7e9fa7eceef88c271d2d89dfe585c2 Mon Sep 17 00:00:00 2001 From: Yaima Valdivia Date: Thu, 18 Feb 2016 15:48:30 -0800 Subject: [PATCH 6/8] Ecobee - Changed tiles order --- .../smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 6af6ecb..944b93a 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -103,7 +103,7 @@ metadata { state "humidity", label:'${currentValue}%' } main "temperature" - details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","resumeProgram", "humidity", "refresh"]) + details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"]) } preferences { From 62aeb0533dc0cc802eb83113f6673e22396dbf24 Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Thu, 18 Feb 2016 17:05:07 -0800 Subject: [PATCH 7/8] Iris motion and contact sensor fingeprints --- .../smartsense-motion-sensor.groovy | 1 + .../smartsense-open-closed-sensor.groovy | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index 10b0740..e787c24 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -29,6 +29,7 @@ metadata { fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor" fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor" } diff --git a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy index ae340b2..8de222d 100644 --- a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy @@ -16,17 +16,18 @@ metadata { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") { - capability "Battery" + capability "Battery" capability "Configuration" - capability "Contact Sensor" + capability "Contact Sensor" capability "Refresh" capability "Temperature Measurement" - command "enrollResponse" + command "enrollResponse" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300" + fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor" } simulator { From f84e21d83ae69f6fe4074b25034d93c4a1ce9893 Mon Sep 17 00:00:00 2001 From: Yaima Valdivia Date: Mon, 22 Feb 2016 14:55:15 -0800 Subject: [PATCH 8/8] Added colors for degrees in Celsius Emergency heat mapped to auxHeatOnly --- .../ecobee-thermostat.groovy | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 944b93a..802449c 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -45,7 +45,16 @@ metadata { valueTile("temperature", "device.temperature", width: 2, height: 2) { state("temperature", label:'${currentValue}°', unit:"F", backgroundColors:[ - [value: 31, color: "#153591"], + // Celsius + [value: 0, color: "#153591"], + [value: 7, color: "#1e9cbb"], + [value: 15, color: "#90d2a7"], + [value: 23, color: "#44b621"], + [value: 28, color: "#f1d801"], + [value: 35, color: "#d04e00"], + [value: 37, color: "#bc2323"], + // Fahrenheit + [value: 40, color: "#153591"], [value: 44, color: "#1e9cbb"], [value: 59, color: "#90d2a7"], [value: 74, color: "#44b621"], @@ -421,6 +430,10 @@ def heat() { generateStatusEvent() } +def emergencyHeat() { + auxHeatOnly() +} + def auxHeatOnly() { log.debug "auxHeatOnly" def deviceId = device.deviceNetworkId.split(/\./).last()