Compare commits

..

3 Commits

8 changed files with 153 additions and 1568 deletions

View File

@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
} }
def parse(String description) { def parse(String description) {
log.debug description
def description_map = parseDescriptionAsMap(description) def description_map = parseDescriptionAsMap(description)
def event_name = "" def event_name = ""
def measurement_map = [ def measurement_map = [
@@ -129,10 +129,7 @@ def parse(String description) {
zigbeedeviceid: device.zigbeeId, zigbeedeviceid: device.zigbeeId,
created: new Date().time /1000 as int created: new Date().time /1000 as int
] ]
if (description_map.cluster == "0000"){ if (description_map.cluster == "0001"){
/* version number, not used */
} else if (description_map.cluster == "0001"){
/* battery voltage in mV (device needs minimium 2.1v to run) */ /* battery voltage in mV (device needs minimium 2.1v to run) */
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}" log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
event_name = "battery_status" event_name = "battery_status"
@@ -158,6 +155,10 @@ def parse(String description) {
def parseDescriptionAsMap(description) { def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param -> (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":") def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] if(nameAndValue.length == 2){
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}else{
map += []
}
} }
} }

View File

@@ -30,7 +30,6 @@ metadata {
command "lowerSetpoint" command "lowerSetpoint"
command "resumeProgram" command "resumeProgram"
command "switchMode" command "switchMode"
command "switchFanMode"
attribute "thermostatSetpoint","number" attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string" attribute "thermostatStatus","string"
@@ -45,16 +44,7 @@ metadata {
valueTile("temperature", "device.temperature", width: 2, height: 2) { valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F", state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[ backgroundColors:[
// Celsius [value: 31, color: "#153591"],
[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: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"], [value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"], [value: 74, color: "#44b621"],
@@ -73,9 +63,10 @@ metadata {
state "updating", label:"Working", icon: "st.secondary.secondary" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto" state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on" state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
state "updating", label:"Working", icon: "st.secondary.secondary" state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
} }
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up" state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
@@ -105,14 +96,14 @@ metadata {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") { standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send" state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
valueTile("humidity", "device.humidity", decoration: "flat") { valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}%' state "humidity", label:'${currentValue}% humidity'
} }
main "temperature" main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"]) details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh", "humidity"])
} }
preferences { preferences {
@@ -163,9 +154,6 @@ def generateEvent(Map results) {
} 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]
} else if (name=="thermostatFanMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="humidity") { } else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString()) isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"] event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
@@ -213,11 +201,11 @@ private getThermostatDescriptionText(name, value, linkText) {
void setHeatingSetpoint(setpoint) { void setHeatingSetpoint(setpoint) {
log.debug "***heating setpoint $setpoint" log.debug "***heating setpoint $setpoint"
def heatingSetpoint = setpoint def heatingSetpoint = setpoint.toDouble()
def coolingSetpoint = device.currentValue("coolingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint").toDouble()
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") def minHeatingSetpoint = device.currentValue("minHeatingSetpoint").toDouble()
//enforce limits of heatingSetpoint //enforce limits of heatingSetpoint
if (heatingSetpoint > maxHeatingSetpoint) { if (heatingSetpoint > maxHeatingSetpoint) {
@@ -250,11 +238,11 @@ void setHeatingSetpoint(setpoint) {
void setCoolingSetpoint(setpoint) { void setCoolingSetpoint(setpoint) {
log.debug "***cooling setpoint $setpoint" log.debug "***cooling setpoint $setpoint"
def heatingSetpoint = device.currentValue("heatingSetpoint") def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
def coolingSetpoint = setpoint def coolingSetpoint = setpoint.toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint").toDouble()
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint").toDouble()
if (coolingSetpoint > maxCoolingSetpoint) { if (coolingSetpoint > maxCoolingSetpoint) {
@@ -315,7 +303,7 @@ def modes() {
} }
def fanModes() { def fanModes() {
["on", "auto"] ["off", "on", "auto", "circulate"]
} }
def switchMode() { def switchMode() {
@@ -344,15 +332,17 @@ def switchFanMode() {
def returnCommand def returnCommand
switch (currentFanMode) { switch (currentFanMode) {
case "on": case "fanAuto":
returnCommand = switchToFanMode("auto") returnCommand = switchToFanMode("fanOn")
break break
case "auto": case "fanOn":
returnCommand = switchToFanMode("on") returnCommand = switchToFanMode("fanCirculate")
break
case "fanCirculate":
returnCommand = switchToFanMode("fanAuto")
break break
} }
if(!currentFanMode) { returnCommand = switchToFanMode("auto") } if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
returnCommand returnCommand
} }
@@ -361,20 +351,25 @@ def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode" log.debug "switching to fan mode: $nextMode"
def returnCommand def returnCommand
if(nextMode == "auto") { if(nextMode == "fanAuto") {
if(!fanModes.contains("auto")) { if(!fanModes.contains("fanAuto")) {
returnCommand = fanAuto() returnCommand = fanAuto()
} else { } else {
returnCommand = switchToFanMode("on") returnCommand = switchToFanMode("fanOn")
} }
} else if(nextMode == "on") { } else if(nextMode == "fanOn") {
if(!fanModes.contains("on")) { if(!fanModes.contains("fanOn")) {
returnCommand = fanOn() returnCommand = fanOn()
} else { } else {
returnCommand = switchToFanMode("auto") returnCommand = switchToFanMode("fanCirculate")
}
} else if(nextMode == "fanCirculate") {
if(!fanModes.contains("fanCirculate")) {
returnCommand = fanCirculate()
} else {
returnCommand = switchToFanMode("fanAuto")
} }
} }
returnCommand returnCommand
} }
@@ -384,10 +379,13 @@ def getDataByName(String name) {
def setThermostatMode(String value) { def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})" log.debug "setThermostatMode({$value})"
} }
def setThermostatFanMode(String value) { def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})" log.debug "setThermostatFanMode({$value})"
} }
def generateModeEvent(mode) { def generateModeEvent(mode) {
@@ -430,10 +428,6 @@ def heat() {
generateStatusEvent() generateStatusEvent()
} }
def emergencyHeat() {
auxHeatOnly()
}
def auxHeatOnly() { def auxHeatOnly() {
log.debug "auxHeatOnly" log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
@@ -478,44 +472,22 @@ def auto() {
def fanOn() { def fanOn() {
log.debug "fanOn" log.debug "fanOn"
String fanMode = "on" // parent.setFanMode (this,"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() { def fanAuto() {
log.debug "fanAuto" log.debug "fanAuto"
String fanMode = "auto" // parent.setFanMode (this,"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 fanCirculate() {
log.debug "fanCirculate"
// parent.setFanMode (this,"circulate")
}
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def fanOff() {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint log.debug "fanOff"
// parent.setFanMode (this,"off")
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 generateSetpointEvent() { def generateSetpointEvent() {
@@ -546,7 +518,6 @@ def generateSetpointEvent() {
coolingSetpoint = roundC(coolingSetpoint) coolingSetpoint = roundC(coolingSetpoint)
} }
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale) sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)

View File

@@ -29,7 +29,6 @@ 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: "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: "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"
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" fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
} }

View File

@@ -16,18 +16,17 @@
metadata { metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" 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-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 { simulator {

View File

@@ -25,7 +25,6 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702" 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: "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, 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) { tiles(scale: 2) {

View File

@@ -257,16 +257,21 @@ def getEcobeeThermostats() {
} }
} else { } else {
log.debug "http status: ${resp.status}" 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 (groovyx.net.http.HttpResponseException e) { } catch(Exception e) {
log.trace "Exception polling children: " + e.response.data.status log.debug "___exception getEcobeeThermostats(): " + e
if (e.response.data.status.code == 14) { refreshAuthToken()
atomicState.action = "getEcobeeThermostats" }
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
}
atomicState.thermostats = stats atomicState.thermostats = stats
return stats return stats
} }
@@ -417,8 +422,7 @@ def pollChildren(child = null) {
heatingSetpoint: stat.runtime.desiredHeat / 10, heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10, coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode, thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity, humidity: stat.runtime.actualHumidity
thermostatFanMode: stat.runtime.desiredFanMode
] ]
if (location.temperatureScale == "F") if (location.temperatureScale == "F")
@@ -445,15 +449,23 @@ def pollChildren(child = null) {
} }
result = true result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" 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 (groovyx.net.http.HttpResponseException e) { } catch(Exception e) {
log.trace "Exception polling children: " + e.response.data.status log.debug "___exception polling children: " + e
if (e.response.data.status.code == 14) { refreshAuthToken()
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
} }
return result return result
} }
@@ -629,14 +641,16 @@ private refreshAuthToken() {
} }
atomicState.action = "" atomicState.action = ""
} else {
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
} }
} }
} 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
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc. if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken") runIn(reAttemptPeriod, "refreshAuthToken")
} else if (e.statusCode == 401) { // unauthorized } else if (e.statusCode == 401) { //refresh token is expired
atomicState.reAttempt = atomicState.reAttempt + 1 atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}" log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) { if (atomicState.reAttempt <= 3) {
@@ -662,19 +676,9 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10 int h = heating * 10
int c = cooling * 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 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) def result = sendJson(child, jsonRequestBody)
return result return result
} }
@@ -709,21 +713,29 @@ def sendJson(child = null, String jsonBody) {
log.debug "Error return code = ${resp.data.status.code}" log.debug "Error return code = ${resp.data.status.code}"
debugEvent("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 (groovyx.net.http.HttpResponseException e) { } catch(Exception e) {
log.trace "Exception Sending Json: " + e.response.data.status log.debug "Exception Sending Json: " + e
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") debugEvent ("Exception Sending JSON: " + e)
if (e.response.data.status.code == 14) { refreshAuthToken()
atomicState.action = "pollChildren" return false
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) if (returnStatus == 0)
return true return true

View File

@@ -16,14 +16,14 @@
* Date: 2013-09-06 * Date: 2013-09-06
*/ */
definition( definition(
name: "Wemo (Connect)", name: "Wemo (Connect)",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.", description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true singleInstance: true
) )
preferences { preferences {
@@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) {
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1 sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
HOST: ${deviceNetworkId} HOST: ${deviceNetworkId}
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"])) """, physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
} }
private verifyDevices() { private verifyDevices() {
@@ -52,13 +52,6 @@ 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() def firstPage()
{ {
if(canInstallLabs()) if(canInstallLabs())
@@ -69,7 +62,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}" log.debug "REFRESH COUNT :: ${refreshCount}"
ssdpSubscribe() subscribe(location, null, locationHandler, [filterEvents:false])
//ssdp request every 25 seconds //ssdp request every 25 seconds
if((refreshCount % 5) == 0) { if((refreshCount % 5) == 0) {
@@ -112,7 +105,9 @@ def devicesDiscovered() {
def motions = getWemoMotions() def motions = getWemoMotions()
def lightSwitches = getWemoLightSwitches() def lightSwitches = getWemoLightSwitches()
def devices = switches + motions + lightSwitches def devices = switches + motions + lightSwitches
devices?.collect{ [app.id, it.ssdpUSN].join('.') } def list = []
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
} }
def switchesDiscovered() { def switchesDiscovered() {
@@ -180,9 +175,8 @@ def updated() {
def initialize() { def initialize() {
unsubscribe() unsubscribe()
unschedule() unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
ssdpSubscribe()
if (selectedSwitches) if (selectedSwitches)
addSwitches() addSwitches()
@@ -195,7 +189,7 @@ def initialize() {
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh") runEvery5Minutes("refresh")
} }
def resubscribe() { def resubscribe() {
@@ -205,7 +199,7 @@ def resubscribe() {
def refresh() { def refresh() {
log.debug "refresh() called" log.debug "refresh() called"
doDeviceSync() doDeviceSync()
refreshDevices() refreshDevices()
} }
@@ -241,14 +235,14 @@ def addSwitches() {
if (!d) { if (!d) {
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}" log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [ d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
"label": selectedSwitch?.value?.name ?: "Wemo Switch", "label": selectedSwitch?.value?.name ?: "Wemo Switch",
"data": [ "data": [
"mac": selectedSwitch.value.mac, "mac": selectedSwitch.value.mac,
"ip": selectedSwitch.value.ip, "ip": selectedSwitch.value.ip,
"port": selectedSwitch.value.port "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}") d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else { } else {
@@ -279,9 +273,9 @@ def addMotions() {
"port": selectedMotion.value.port "port": selectedMotion.value.port
] ]
]) ])
def ipvalue = convertHexToIP(selectedMotion.value.ip) def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
} }
@@ -310,147 +304,26 @@ def addLightSwitches() {
"port": selectedLightSwitch.value.port "port": selectedLightSwitch.value.port
] ]
]) ])
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip) def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
} }
} }
} }
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 locationHandler(evt) {
def description = evt.description def description = evt.description
def hub = evt?.hubId def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description) def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub] parsedEvent << ["hub":hub]
log.debug parsedEvent log.debug parsedEvent
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) { if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
def switches = getWemoSwitches() def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) { if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist //if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { } else {
log.debug "Device was already found in state..." log.debug "Device was already found in state..."
@@ -462,16 +335,16 @@ def locationHandler(evt) {
d.port = parsedEvent.port d.port = parsedEvent.port
deviceChangedValues = true deviceChangedValues = true
log.debug "Device's port or ip changed..." 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.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll() child.poll()
} }
} }
} }
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) { else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
def motions = getWemoMotions() def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) { if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist //if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { // just update the values } else { // just update the values
log.debug "Device was already found in state..." log.debug "Device was already found in state..."
@@ -586,7 +459,6 @@ def locationHandler(evt) {
} }
} }
@Deprecated
private def parseXmlBody(def body) { private def parseXmlBody(def body) {
def decodedBytes = body.decodeBase64() def decodedBytes = body.decodeBase64()
def bodyString def bodyString
@@ -668,4 +540,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() { private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it } return location.hubs*.firmwareVersionString.findAll { it }
} }