Compare commits

...

32 Commits

Author SHA1 Message Date
Kim Jeongkyu
eb3d2d001d MSA-899: Astralink Lifecare Dev 2016-02-23 02:52:49 -06:00
Yaima
a4ebe87f4e Merge pull request #541 from Yaima/master
Added colors for degrees in Celsius - https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-22 14:59:31 -08:00
Yaima Valdivia
f84e21d83a Added colors for degrees in Celsius
Emergency heat  mapped to auxHeatOnly
2016-02-22 14:55:15 -08:00
Yaima
85175eb298 Merge pull request #529 from Yaima/master
Ecobee - Changed tiles order
2016-02-19 14:45:01 -08:00
Luke Bredeson
c75568bcf1 Merge pull request #518 from lbredeso/wemo-subscription-improvements
INSIDE-787: Improve Wemo (Connect) event subscriptions
2016-02-19 10:10:23 -06:00
Tom Manley
edc98e4840 Merge pull request #497 from tpmanley/feature/outlet_fingerprint
outlet: Added fingerprint for new ST outlet
2016-02-18 19:25:39 -06:00
Vinay Rao
fb2c2cb2a7 Merge pull request #530 from workingmonk/iris_motion_contact
[DEVC-259] Iris motion and contact sensor fingerprints
2016-02-18 17:22:56 -08:00
Vinay Rao
62aeb0533d Iris motion and contact sensor fingeprints 2016-02-18 17:07:11 -08:00
Yaima Valdivia
fb6cbcc35e Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# Via Yaima
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
2016-02-18 15:50:54 -08:00
Yaima Valdivia
097584944e Ecobee - Changed tiles order 2016-02-18 15:48:30 -08:00
Yaima
01fae3dcd4 Merge pull request #525 from Yaima/master
Better exception handling for Ecobee
2016-02-18 15:13:34 -08:00
Yaima Valdivia
6c125fe80f Better exception handling of Ecobee
Refreshing only if status code 14 - Authentication token has expired.
Refresh your tokens.
2016-02-18 14:49:26 -08:00
Yaima
5728f08770 Merge pull request #522 from Yaima/master
Fixed Ecobee - HH errors
2016-02-17 13:28:40 -08:00
Yaima Valdivia
f073df0a57 Fixed Ecobee - HH errors 2016-02-17 13:28:10 -08:00
Yaima
2af0db4e89 Merge pull request #520 from Yaima/master
Ecobee fanMode available - https://smartthings.atlassian.net/browse/D…
2016-02-17 11:30:43 -08:00
Yaima Valdivia
24bfb7f20f Ecobee fanMode available - https://smartthings.atlassian.net/browse/DVCSMP-1501
https://smartthings.atlassian.net/browse/DVCSMP-1501
2016-02-17 11:15:26 -08:00
Luke Bredeson
9263107f0e INSIDE-787: Improve Wemo (Connect) event subscriptions 2016-02-16 17:05:09 -06:00
Yaima
41b9d71e3d Merge pull request #516 from Yaima/master
Ecobee 3 - https://smartthings.atlassian.net/browse/DEVC-285
2016-02-16 14:52:55 -08:00
Duncan McKee
e60a9d1925 Merge pull request #487 from dantheman2865/MSA-866-2
MSA-866: Update SmartSense Moisture to include Temperature Measurement from WWA02AA
2016-02-16 17:48:29 -05:00
Yaima Valdivia
27b7c24536 Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# By Juan Pablo Risso (3) and others
# Via Kris Schaller (24) and others
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
  PROB-870 - Harmony fails to save credentials
  add missing translations
  add event translation
  Removed canInstallLabs()
  remove segmented style input to prevent iOS crash
  PROB-537 - Fix error in line 335
  MSA-68: Spruce Irrigation controller and soil moisture sensors.
  # This is a combination of 3 commits. # The first commit's message is: MSA-68: Spruce Irrigation controller and soil moisture sensors.
  DVCSMP-1480 Fixed ArrayIndexOutOfBoundsException
  Convert closure to method
  Revert "Convert closure to method"
  Closure was causing sandbox issues locally
  Bugfixes for codeReports
  Fix Homeseer Multi Instance encap parse PROB-398
  Merge pull request #135 from kwarodom/fibaroSmokeSensor
2016-02-16 14:12:07 -08:00
Yaima Valdivia
13d9137c9a Ecobee 3 - https://smartthings.atlassian.net/browse/DEVC-285
https://smartthings.atlassian.net/browse/DEVC-285
https://smartthings.atlassian.net/browse/DVCSMP-1431
2016-02-16 13:48:47 -08:00
Juan Pablo Risso
b672a0b810 Merge pull request #514 from juano2310/logitech_hotfix
PROB-870 - Harmony fails to save credentials
2016-02-16 15:47:26 -05:00
Dylan Bijnagte
2b6a6a47ce Merge pull request #375 from Bijnagte/notify-me-when-i18n-events
add event translation
2016-02-16 14:39:29 -06:00
Juan Pablo Risso
7d07b93694 PROB-870 - Harmony fails to save credentials
It seams like the user removed some activities on Harmony side without removing them from SmartThings. This is causing an issue when adding new activities. This fix checks if the activity still exists before creating a new device.
2016-02-16 14:38:37 -05:00
dylanbijnagte
512bd3adc4 add missing translations 2016-02-16 11:30:20 -06:00
Steve Vlaminck
7f707a9dbb Merge pull request #512 from vlaminck/gentleWakeUp-iOS-crash-fix
remove segmented style input to prevent iOS crash
2016-02-16 10:24:47 -06:00
dylanbijnagte
86e097ba0a add event translation 2016-02-16 10:05:46 -06:00
Juan Pablo Risso
89ec1f207f Merge pull request #513 from juano2310/hue_bu
Removed canInstallLabs()
2016-02-16 10:07:45 -05:00
Juan Pablo Risso
664af57708 Removed canInstallLabs() 2016-02-16 09:35:21 -05:00
vlaminck
d9aa1e378d remove segmented style input to prevent iOS crash 2016-02-15 21:39:06 -06:00
Tom Manley
6c5b93da87 outlet: Added fingerprint for new ST outlet
Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1360
2016-02-10 10:43:21 -06:00
Daniel Kurin
b7484ff0b8 MSA-866: Currently, the SmartSense Moisture handles both the FortrezZ WWA01 and the '02, however the '02 sends a temperature measurement. This PR expands the existing DH to add a valueTile with that temperature data (per https://github.com/SmartThingsCommunity/SmartThingsPublic/pull/447) 2016-02-07 11:36:24 -05:00
14 changed files with 1964 additions and 455 deletions

View File

@@ -22,10 +22,6 @@ metadata {
capability "Polling" capability "Polling"
} }
simulator {
// TODO: define status and reply messages here
}
tiles { tiles {
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",
@@ -56,16 +52,12 @@ metadata {
} }
def refresh() { def refresh() {
log.debug "refresh..." log.debug "refresh called"
poll() poll()
} }
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChildren(this) parent.pollChild(this)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
} }

View File

@@ -19,34 +19,49 @@ metadata {
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") { definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Thermostat" capability "Thermostat"
capability "Temperature Measurement"
capability "Polling" capability "Polling"
capability "Sensor" capability "Sensor"
capability "Refresh" capability "Refresh"
capability "Relative Humidity Measurement"
command "generateEvent" command "generateEvent"
command "raiseSetpoint" command "raiseSetpoint"
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"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number"
} }
simulator { } tiles {
tiles {
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:[
[value: 31, color: "#153591"], // Celsius
[value: 44, color: "#1e9cbb"], [value: 0, color: "#153591"],
[value: 59, color: "#90d2a7"], [value: 7, color: "#1e9cbb"],
[value: 74, color: "#44b621"], [value: 15, color: "#90d2a7"],
[value: 84, color: "#f1d801"], [value: 23, color: "#44b621"],
[value: 95, color: "#d04e00"], [value: 28, color: "#f1d801"],
[value: 96, color: "#bc2323"] [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"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
) )
} }
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
@@ -58,10 +73,9 @@ 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", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on" state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off" state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate" state "updating", label:"Working", icon: "st.secondary.secondary"
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"
@@ -91,11 +105,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 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" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}%'
}
main "temperature" main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"]) details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
} }
preferences { preferences {
@@ -107,8 +124,6 @@ metadata {
// parse events into attributes // parse events into attributes
def parse(String description) { def parse(String description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
// TODO: handle '' attribute
} }
def refresh() { def refresh() {
@@ -133,16 +148,27 @@ def generateEvent(Map results) {
def isChange = false def isChange = false
def isDisplayed = true def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText), def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name] handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") { if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //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
isChange = isTemperatureStateChange(device, name, value.toString()) isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed] event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false]
} 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") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else { } else {
isChange = isStateChange(device, name, value.toString()) isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
@@ -158,13 +184,19 @@ def generateEvent(Map results) {
//return descriptionText to be shown on mobile activity feed //return descriptionText to be shown on mobile activity feed
private getThermostatDescriptionText(name, value, linkText) { private getThermostatDescriptionText(name, value, linkText) {
if(name == "temperature") { if(name == "temperature") {
return "$linkText temperature is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "$linkText temperature is $sendValue ${location.temperatureScale}"
} else if(name == "heatingSetpoint") { } else if(name == "heatingSetpoint") {
return "heating setpoint is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "heating setpoint is $sendValue ${location.temperatureScale}"
} else if(name == "coolingSetpoint"){ } else if(name == "coolingSetpoint"){
return "cooling setpoint is $value°F" def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "cooling setpoint is $sendValue ${location.temperatureScale}"
} else if (name == "thermostatMode") { } else if (name == "thermostatMode") {
return "thermostat mode is ${value}" return "thermostat mode is ${value}"
@@ -172,26 +204,26 @@ private getThermostatDescriptionText(name, value, linkText) {
} else if (name == "thermostatFanMode") { } else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}" return "thermostat fan mode is ${value}"
} else if (name == "humidity") {
return "humidity is ${value} %"
} else { } else {
return "${name} = ${value}" return "${name} = ${value}"
} }
} }
void setHeatingSetpoint(setpoint) { void setHeatingSetpoint(setpoint) {
setHeatingSetpoint(setpoint.toDouble()) log.debug "***heating setpoint $setpoint"
}
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = setpoint def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble() def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
//enforce limits of heatingSetpoint //enforce limits of heatingSetpoint
if (heatingSetpoint > 79) { if (heatingSetpoint > maxHeatingSetpoint) {
heatingSetpoint = 79 heatingSetpoint = maxHeatingSetpoint
} else if (heatingSetpoint < 45) { } else if (heatingSetpoint < minHeatingSetpoint) {
heatingSetpoint = 45 heatingSetpoint = minHeatingSetpoint
} }
//enforce limits of heatingSetpoint vs coolingSetpoint //enforce limits of heatingSetpoint vs coolingSetpoint
@@ -201,32 +233,34 @@ void setHeatingSetpoint(Double setpoint) {
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
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 (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) 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()
} else { } else {
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app log.error "Error setHeatingSetpoint(setpoint)"
} }
} }
void setCoolingSetpoint(setpoint) { void setCoolingSetpoint(setpoint) {
setCoolingSetpoint(setpoint.toDouble()) log.debug "***cooling setpoint $setpoint"
} def heatingSetpoint = device.currentValue("heatingSetpoint")
void setCoolingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
def coolingSetpoint = setpoint def coolingSetpoint = setpoint
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (coolingSetpoint > 92) {
coolingSetpoint = 92 if (coolingSetpoint > maxCoolingSetpoint) {
} else if (coolingSetpoint < 65) { coolingSetpoint = maxCoolingSetpoint
coolingSetpoint = 65 } else if (coolingSetpoint < minCoolingSetpoint) {
coolingSetpoint = minCoolingSetpoint
} }
//enforce limits of heatingSetpoint vs coolingSetpoint //enforce limits of heatingSetpoint vs coolingSetpoint
@@ -236,15 +270,18 @@ void setCoolingSetpoint(Double setpoint) {
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
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 (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) { if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) 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()
} else { } else {
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app log.error "Error setCoolingSetpoint(setpoint)"
} }
} }
@@ -278,7 +315,7 @@ def modes() {
} }
def fanModes() { def fanModes() {
["off", "on", "auto", "circulate"] ["on", "auto"]
} }
def switchMode() { def switchMode() {
@@ -307,17 +344,15 @@ def switchFanMode() {
def returnCommand def returnCommand
switch (currentFanMode) { switch (currentFanMode) {
case "fanAuto": case "on":
returnCommand = switchToFanMode("fanOn") returnCommand = switchToFanMode("auto")
break break
case "fanOn": case "auto":
returnCommand = switchToFanMode("fanCirculate") returnCommand = switchToFanMode("on")
break
case "fanCirculate":
returnCommand = switchToFanMode("fanAuto")
break break
} }
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") } if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
returnCommand returnCommand
} }
@@ -326,25 +361,20 @@ def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode" log.debug "switching to fan mode: $nextMode"
def returnCommand def returnCommand
if(nextMode == "fanAuto") { if(nextMode == "auto") {
if(!fanModes.contains("fanAuto")) { if(!fanModes.contains("auto")) {
returnCommand = fanAuto() returnCommand = fanAuto()
} else { } else {
returnCommand = switchToFanMode("fanOn") returnCommand = switchToFanMode("on")
} }
} else if(nextMode == "fanOn") { } else if(nextMode == "on") {
if(!fanModes.contains("fanOn")) { if(!fanModes.contains("on")) {
returnCommand = fanOn() returnCommand = fanOn()
} else { } else {
returnCommand = switchToFanMode("fanCirculate") returnCommand = switchToFanMode("auto")
}
} else if(nextMode == "fanCirculate") {
if(!fanModes.contains("fanCirculate")) {
returnCommand = fanCirculate()
} else {
returnCommand = switchToFanMode("fanAuto")
} }
} }
returnCommand returnCommand
} }
@@ -354,13 +384,10 @@ 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) {
@@ -403,6 +430,10 @@ 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()
@@ -447,26 +478,44 @@ def auto() {
def fanOn() { def fanOn() {
log.debug "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() { def fanAuto() {
log.debug "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 fanCirculate() { def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
log.debug "fanCirculate" def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
// parent.setFanMode (this,"circulate")
}
def fanOff() {
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() {
@@ -476,20 +525,42 @@ def generateSetpointEvent() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}" log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
log.debug "Heating Setpoint = ${heatingSetpoint}" log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
log.debug "Cooling Setpoint = ${coolingSetpoint}" log.debug "Cooling Setpoint = ${coolingSetpoint}"
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if(location.temperatureScale == "C")
{
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
minHeatingSetpoint = roundC(minHeatingSetpoint)
minCoolingSetpoint = roundC(minCoolingSetpoint)
heatingSetpoint = roundC(heatingSetpoint)
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)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") { if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
} }
else if (mode == "cool") { else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -499,9 +570,9 @@ def generateSetpointEvent() {
sendEvent("name":"thermostatSetpoint", "value":"Off") sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "emergencyHeat") { } else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
} }
@@ -510,26 +581,30 @@ def generateSetpointEvent() {
void raiseSetpoint() { void raiseSetpoint() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def targetvalue
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
if (mode == "off" || mode == "auto") { if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint" log.warn "this mode: $mode does not allow raiseSetpoint"
} else { } else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger() def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) { if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else { } else {
targetvalue = 0 targetvalue = 0
} }
targetvalue = targetvalue + 1 targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
if (mode == "heat" && targetvalue > 79) { if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
targetvalue = 79 targetvalue = maxHeatingSetpoint
} else if (mode == "cool" && targetvalue > 92) { } else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
targetvalue = 92 targetvalue = maxCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -543,25 +618,29 @@ void raiseSetpoint() {
void lowerSetpoint() { void lowerSetpoint() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def targetvalue
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (mode == "off" || mode == "auto") { if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint" log.warn "this mode: $mode does not allow lowerSetpoint"
} else { } else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger() def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) { if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else { } else {
targetvalue = 0 targetvalue = 0
} }
targetvalue = targetvalue - 1 targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
if (mode == "heat" && targetvalue.toInteger() < 45) { if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
targetvalue = 45 targetvalue = minHeatingSetpoint
} else if (mode == "cool" && targetvalue.toInteger() < 65) { } else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
targetvalue = 65 targetvalue = minCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -575,15 +654,15 @@ void lowerSetpoint() {
void alterSetpoint(temp) { void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint def targetHeatingSetpoint
def targetCoolingSetpoint def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud //step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat"){ if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){ if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value targetCoolingSetpoint = temp.value
@@ -602,19 +681,22 @@ void alterSetpoint(temp) {
} }
} }
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " + log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}" "coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
//step2: call parent.setHold to send http request to 3rd party cloud
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) { def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false) def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint) sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint) 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()"
if (mode == "heat"){ if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") { } else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false) sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
@@ -626,9 +708,9 @@ void alterSetpoint(temp) {
def generateStatusEvent() { def generateStatusEvent() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature").toInteger() def temperature = device.currentValue("temperature")
def statusText def statusText
@@ -643,14 +725,14 @@ def generateStatusEvent() {
if (temperature >= heatingSetpoint) if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Heating to ${heatingSetpoint}° F" statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
} else if (mode == "cool") { } else if (mode == "cool") {
if (temperature <= coolingSetpoint) if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Cooling to ${coolingSetpoint}° F" statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -660,7 +742,7 @@ def generateStatusEvent() {
statusText = "Right Now: Off" statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") { } else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat" statusText = "Emergency Heat"
@@ -673,7 +755,18 @@ def generateStatusEvent() {
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true) sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
} }
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) { def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true) sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
} }
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}
def convertFtoC (tempF) {
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

@@ -16,6 +16,7 @@ metadata {
capability "Water Sensor" capability "Water Sensor"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Temperature Measurement"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86" fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86" fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
@@ -39,17 +40,29 @@ metadata {
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0" attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
} }
} }
standardTile("temperature", "device.temperature", width: 2, height: 2) { standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff" state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0" state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000" state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
} }
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:"" state "battery", label:'${currentValue}% battery', unit:""
} }
main (["water", "temperatureState"])
main (["water", "temperature"]) details(["water", "temperatureState", "temperature", "battery"])
details(["water", "temperature", "battery"])
} }
} }
@@ -115,7 +128,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map.descriptionText = "${device.displayName} is ${map.value}" map.descriptionText = "${device.displayName} is ${map.value}"
} }
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) { if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
map.name = "temperature" map.name = "temperatureState"
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"} if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"} if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"} if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
@@ -129,17 +142,30 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map map
} }
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
{ {
def map = [:] def map = [:]
map.name = "water" if(cmd.sensorType == 1) {
map.value = cmd.value ? "wet" : "dry" map.name = "temperature"
map.descriptionText = "${device.displayName} is ${map.value}" if(cmd.scale == 0) {
map.value = getTemperature(cmd.scaledSensorValue)
} else {
map.value = cmd.scaledSensorValue
}
map.unit = location.temperatureScale
}
map map
} }
def getTemperature(value) {
if(location.temperatureScale == "C"){
return value
} else {
return Math.round(celsiusToFahrenheit(value))
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) def zwaveEvent(physicalgraph.zwave.Command cmd)
{ {
log.debug "COMMAND CLASS: $cmd" log.debug "COMMAND CLASS: $cmd"
} }

View File

@@ -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: "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,17 +16,18 @@
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,6 +25,7 @@ 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

@@ -28,7 +28,7 @@ definition(
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
singleInstance: true singleInstance: true
) { ) {
appSetting "clientId" appSetting "clientId"
} }
@@ -61,7 +61,7 @@ def authPage() {
description = "Click to enter Ecobee Credentials" description = "Click to enter Ecobee Credentials"
} }
def redirectUrl = buildRedirectUrl //"${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}" def redirectUrl = buildRedirectUrl
log.debug "RedirectUrl = ${redirectUrl}" log.debug "RedirectUrl = ${redirectUrl}"
// 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) {
@@ -103,7 +103,7 @@ def oauthInitUrl() {
scope: "smartRead,smartWrite", scope: "smartRead,smartWrite",
client_id: smartThingsClientId, client_id: smartThingsClientId,
state: atomicState.oauthInitState, state: atomicState.oauthInitState,
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback" redirect_uri: callbackUrl
] ]
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}") redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
@@ -115,14 +115,13 @@ def callback() {
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
//verify oauthState == atomicState.oauthInitState, so the callback corresponds to the authentication request
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 //"https://graph.api.smartthings.com/oauth/callback" redirect_uri: callbackUrl
] ]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}" def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -247,31 +246,26 @@ def getEcobeeThermostats() {
] ]
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 = stat.remoteSensors atomicState.remoteSensors = stat.remoteSensors
def dni = [app.id, stat.identifier].join('.') def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat) stats[dni] = getThermostatDisplayName(stat)
} }
} else { } else {
log.debug "http status: ${resp.status}" log.debug "http status: ${resp.status}"
//refresh the auth token }
if (resp.status == 500 && resp.data.status.code == 14) { }
log.debug "Storing the failed action to try later" } catch (groovyx.net.http.HttpResponseException e) {
atomicState.action = "getEcobeeThermostats" log.trace "Exception polling children: " + e.response.data.status
log.debug "Refreshing your auth_token!" if (e.response.data.status.code == 14) {
refreshAuthToken() atomicState.action = "getEcobeeThermostats"
} else { log.debug "Refreshing your auth_token!"
log.error "Authentication error, invalid authentication method, lack of credentials, etc." refreshAuthToken()
}
}
} }
} catch(Exception e) {
log.debug "___exception getEcobeeThermostats(): " + e
refreshAuthToken()
} }
atomicState.thermostats = stats atomicState.thermostats = stats
return stats return stats
@@ -317,7 +311,7 @@ def initialize() {
def devices = thermostats.collect { dni -> def devices = thermostats.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"Ecobee Thermostat:${atomicState.thermostats[dni]}"]) d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"${atomicState.thermostats[dni]}" ?: "Ecobee Thermostat"])
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"
@@ -328,7 +322,7 @@ def initialize() {
def sensors = ecobeesensors.collect { dni -> def sensors = ecobeesensors.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"Ecobee Sensor:${atomicState.sensors[dni]}"]) d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"${atomicState.sensors[dni]}" ?:"Ecobee Sensor"])
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"
@@ -354,21 +348,17 @@ def initialize() {
atomicState.thermostatData = [:] //reset Map to store thermostat data atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected //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)
state.timeSendPush = null atomicState.timeSendPush = null
atomicState.reAttempt = 0
pollHandler() //first time polling data data from thermostat pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins //automatically update devices status every 5 mins
runEvery5Minutes("poll") runEvery5Minutes("poll")
//since access_token expires every 2 hours
runEvery1Hour("refreshAuthToken")
atomicState.reAttempt = 0
} }
def pollHandler() { def pollHandler() {
@@ -389,18 +379,10 @@ 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 jsonRequestBody = '{"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
// // TODO: test this:
//
// def jsonRequestBody = toJson([
// selection:[
// selectionType: "thermostats",
// selectionMatch: getChildDeviceIdsString(),
// includeRuntime: true
// ]
// ])
def pollParams = [ def pollParams = [
uri: apiEndpoint, uri: apiEndpoint,
@@ -411,11 +393,6 @@ def pollChildren(child = null) {
try{ try{
httpGet(pollParams) { resp -> httpGet(pollParams) { resp ->
// if (resp.data) {
// debugEventFromParent(child, "pollChildren(child) >> resp.status = ${resp.status}, resp.data = ${resp.data}")
// }
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
@@ -426,45 +403,57 @@ def pollChildren(child = null) {
log.debug "updating dni $dni" log.debug "updating dni $dni"
def data = [ data = [
coolMode: (stat.settings.coolStages > 0), coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 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, autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler), auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: stat.runtime.actualTemperature / 10, temperature: (stat.runtime.actualTemperature / 10),
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,
thermostatFanMode: stat.runtime.desiredFanMode
] ]
data["temperature"] = data["temperature"] ? data["temperature"].toDouble().toInteger() : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? data["heatingSetpoint"].toDouble().toInteger() : data["heatingSetpoint"] if (location.temperatureScale == "F")
data["coolingSetpoint"] = data["coolingSetpoint"] ? data["coolingSetpoint"].toDouble().toInteger() : data["coolingSetpoint"] {
// debugEventFromParent(child, "Event Data = ${data}") 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] collector[dni] = [data:data]
return collector return collector
} }
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.status == 500 && resp.data.status.code == 14) {
log.debug "Storing the failed action to try later"
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) { } catch (groovyx.net.http.HttpResponseException e) {
log.debug "___exception polling children: " + e log.trace "Exception polling children: " + e.response.data.status
// debugEventFromParent(child, "___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
} }
@@ -476,18 +465,14 @@ def pollChild(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]
// debugEventFromParent(child, "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}") //TODO comment
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent child.generateEvent(tData.data) //parse received message from parent
// return tData.data
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) { } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
// debugEventFromParent(child, "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling") //TODO comment
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null return null
} }
} }
} else { } else {
// debugEventFromParent(child, "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling") //TODO comment
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling" log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
return null return null
} }
@@ -513,9 +498,6 @@ def availableModes(child) {
{ {
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"
// TODO: flag device as in error state
// child.errorState = true
return null return null
} }
@@ -542,8 +524,6 @@ 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"
// TODO: flag device as in error state
// child.errorState = true
return null return null
} }
@@ -561,8 +541,12 @@ def updateSensorData() {
def occupancy = "" def occupancy = ""
it.capability.each { it.capability.each {
if (it.type == "temperature") { if (it.type == "temperature") {
temperature = it.value as Double if (location.temperatureScale == "F") {
temperature = (temperature / 10).toInteger() temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {
if(it.value == "true") if(it.value == "true")
occupancy = "active" occupancy = "active"
@@ -575,7 +559,6 @@ def updateSensorData() {
if(d) { if(d) {
d.sendEvent(name:"temperature", value: temperature) d.sendEvent(name:"temperature", value: temperature)
d.sendEvent(name:"motion", value: occupancy) d.sendEvent(name:"motion", value: occupancy)
// debugEventFromParent(d, "temperature : ${temperature}, motion:${occupancy}")
} }
} }
} }
@@ -595,68 +578,65 @@ def toQueryString(Map m) {
} }
private refreshAuthToken() { private refreshAuthToken() {
log.debug "refreshing auth token" log.debug "refreshing auth token"
if(!atomicState.refreshToken) { if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored" log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else { } else {
def refreshParams = [ def refreshParams = [
method: 'POST', method: 'POST',
uri : apiEndpoint, uri : apiEndpoint,
path : "/token", path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId], query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
] ]
log.debug refreshParams log.debug refreshParams
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!")
log.debug resp log.debug resp
jsonMap = resp.data jsonMap = resp.data
if(resp.data) { if(resp.data) {
log.debug resp.data log.debug resp.data
debugEvent("Response = ${resp.data}") debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}") debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}") debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") { if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}" log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"() "${atomicState.action}"()
//remove saved action atomicState.action = ""
atomicState.action = "" }
}
} }
atomicState.action = "" 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}"
} catch(Exception e) {
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) { //refresh token is expired } else if (e.statusCode == 401) { // unauthorized
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) {
@@ -665,20 +645,16 @@ private refreshAuthToken() {
sendPushAndFeeds(notificationMessage) sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0 atomicState.reAttempt = 0
} }
} }
} }
} }
} }
def resumeProgram(child, deviceId) { def resumeProgram(child, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "resumeProgram children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
//, { "type": "sendMessage", "params": { "text": "Setpoint Updated" } }
def result = sendJson(jsonRequestBody) def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "resumeProgram(child) with result ${result}")
return result return result
} }
@@ -686,28 +662,27 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10 int h = heating * 10
int c = cooling * 10 int c = cooling * 10
// log.debug "setpoints____________ - h: $heating - $h, c: $cooling - $c"
// def thermostatIdsString = getChildDeviceIdsString()
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 jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}, { "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": "indefinite" } } ]}'
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)
// debugEventFromParent(child, "setHold: heating: ${h}, cooling: ${c} with result ${result}")
return result return result
} }
def setMode(child, mode, deviceId) { def setMode(child, mode, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "setCoolingSetpoint children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}' def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
// log.debug "Mode Request Body = ${jsonRequestBody}"
// debugEvent ("Mode Request Body = ${jsonRequestBody}")
def result = sendJson(jsonRequestBody) def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "setMode to ${mode} with result ${result}")
return result return result
} }
@@ -724,8 +699,6 @@ def sendJson(child = null, String jsonBody) {
try{ try{
httpPost(cmdParams) { resp -> httpPost(cmdParams) { resp ->
// debugEventFromParent(child, "sendJson >> resp.status ${resp.status}, resp.data: ${resp.data}")
if(resp.status == 200) { if(resp.status == 200) {
log.debug "updated ${resp.data}" log.debug "updated ${resp.data}"
@@ -736,30 +709,21 @@ 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 == 500 && resp.status.code == 14) {
//log.debug "Storing the failed action to try later"
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) { } catch (groovyx.net.http.HttpResponseException e) {
log.debug "Exception Sending Json: " + e log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("Exception Sending JSON: " + e) debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
refreshAuthToken() if (e.response.data.status.code == 14) {
return false 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) if (returnStatus == 0)
return true return true
@@ -794,25 +758,37 @@ def debugEventFromParent(child, message) {
//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()
} }
} else { } else {
sendPush("Your Ecobee thermostat " + notificationMessage) sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now() atomicState.timeSendPush = now()
} }
atomicState.authToken = null atomicState.authToken = null
} }
def sendActivityFeeds(notificationMessage) { def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices() def devices = getChildDevices()
devices.each { child -> devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
} }
}
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) {
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

@@ -201,8 +201,8 @@ def completionPage() {
section("Switches") { section("Switches") {
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true) input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
if (completionSwitches || androidClient()) { if (completionSwitches) {
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], style: "segmented", defaultValue: "on") input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], defaultValue: "on")
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)") input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
} }
} }

View File

@@ -35,23 +35,11 @@ preferences {
} }
def mainPage() { def mainPage() {
if(canInstallLabs()) { def bridges = bridgesDiscovered()
def bridges = bridgesDiscovered() if (state.username && bridges) {
if (state.username && bridges) { return bulbDiscovery()
return bulbDiscovery()
} else {
return bridgeDiscovery()
}
} else { } else {
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date. return bridgeDiscovery()
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"bridgeDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade") {
paragraph "$upgradeNeeded"
}
}
} }
} }
@@ -765,10 +753,6 @@ private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".") [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
} }
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware) { private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware } return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
} }

View File

@@ -419,9 +419,11 @@ def addDevice() {
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni } def newAction = state.HarmonyActivities.find { it.key == dni }
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"]) if (newAction) {
log.trace "created ${d.displayName} with id $dni" d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
poll() log.trace "created ${d.displayName} with id $dni"
poll()
}
} else { } else {
log.trace "found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }

View File

@@ -0,0 +1,31 @@
'''Acceleration Detected'''.ko=가속화 감지됨
'''Arrival Of'''.ko=도착
'''Both Push and SMS?'''.ko=푸시 메시지와 SMS를 모두 사용하시겠습니까?
'''Button Pushed'''.ko=버튼이 눌렸습니다
'''Contact Closes'''.ko=접점 닫힘
'''Contact Opens'''.ko=접점 열림
'''Departure Of'''.ko=출발
'''Message Text'''.ko=문자 메시지
'''Minutes'''.ko=
'''Motion Here'''.ko=동작
'''Phone Number (for SMS, optional)'''.ko=휴대전화 번호(문자 메시지 - 옵션)
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
'''Smoke Detected'''.ko=연기가 감지되었습니다
'''Switch Turned Off'''.ko=스위치 꺼짐
'''Switch Turned On'''.ko=스위치 꺼짐
'''Choose one or more, when...'''.ko=다음의 경우 하나 이상 선택
'''Yes'''.ko=
'''No'''.ko=아니요
'''Send this message (optional, sends standard status message if not specified)'''.ko=이 메시지 전송(선택적, 지정되지 않은 경우 표준 상태 메시지를 보냅니다)
'''Via a push notification and/or an SMS message'''.ko=푸시 알림 및/또는 문자 메시지를 통해
'''Set for specific mode(s)'''.ko=특정 모드 설정
'''Tap to set'''.ko=눌러서 설정
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지작 간 최소 시간(선택 사항, 모든 메시지의 기본 설정)
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 거주자는 적절한 국가 코드를 입력했는지 확인하십시오
'''Water Sensor Wet'''.ko=Water Sensor에서 물이 감지되었습니다
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''Assign a name'''.ko=이름 배정
'''Choose Modes'''.ko=모드 선택

View File

@@ -20,19 +20,19 @@
* 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell. * 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell.
*/ */
definition( definition(
name: "Notify Me When", name: "Notify Me When",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Receive notifications when anything happens in your home.", description: "Receive notifications when anything happens in your home.",
category: "Convenience", category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
) )
preferences { preferences {
section("Choose one or more, when..."){ section("Choose one or more, when..."){
input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
@@ -47,11 +47,11 @@ preferences {
input "messageText", "text", title: "Message Text", required: false input "messageText", "text", title: "Message Text", required: false
} }
section("Via a push notification and/or an SMS message"){ section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") { input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code" paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"] input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
} }
} }
section("Minimum time between messages (optional, defaults to every message)") { section("Minimum time between messages (optional, defaults to every message)") {
input "frequency", "decimal", title: "Minutes", required: false input "frequency", "decimal", title: "Minutes", required: false
@@ -71,7 +71,7 @@ def updated() {
def subscribeToEvents() { def subscribeToEvents() {
subscribe(button, "button.pushed", eventHandler) //tw subscribe(button, "button.pushed", eventHandler) //tw
subscribe(contact, "contact.open", eventHandler) subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler) subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler) subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler) subscribe(motion, "motion.active", eventHandler)
@@ -99,49 +99,55 @@ def eventHandler(evt) {
} }
private sendMessage(evt) { private sendMessage(evt) {
def msg = messageText ?: defaultText(evt) String msg = messageText
Map options = [:]
if (!messageText) {
msg = defaultText(evt)
options = [translatable: true, triggerEvent: evt]
}
log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'" log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients) sendNotificationToContacts(msg, recipients, options)
} } else {
else { if (!phone || pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
//sendPush(msg)
}
if (phone) {
options.phone = phone
log.debug 'sending SMS'
//sendSms(phone, msg)
}
sendNotification(msg, options)
}
if (!phone || pushAndPhone != "No") {
log.debug "sending push"
sendPush(msg)
}
if (phone) {
log.debug "sending SMS"
sendSms(phone, msg)
}
}
if (frequency) { if (frequency) {
state[evt.deviceId] = now() state[evt.deviceId] = now()
} }
} }
private defaultText(evt) { private defaultText(evt) {
if (evt.name == "presence") { if (evt.name == 'presence') {
if (evt.value == "present") { if (evt.value == 'present') {
if (includeArticle) { if (includeArticle) {
"$evt.linkText has arrived at the $location.name" '{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'
} }
else { else {
"$evt.linkText has arrived at $location.name" '{{ triggerEvent.linkText }} has arrived at {{ location.name }}'
} }
} } else {
else {
if (includeArticle) { if (includeArticle) {
"$evt.linkText has left the $location.name" '{{ triggerEvent.linkText }} has left the {{ location.name }}'
} }
else { else {
"$evt.linkText has left $location.name" '{{ triggerEvent.linkText }} has left {{ location.name }}'
} }
} }
} } else {
else { '{{ triggerEvent.descriptionText }}'
evt.descriptionText
} }
} }

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}")) """, physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
} }
private verifyDevices() { 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() def firstPage()
{ {
if(canInstallLabs()) if(canInstallLabs())
@@ -62,7 +69,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}" log.debug "REFRESH COUNT :: ${refreshCount}"
subscribe(location, null, locationHandler, [filterEvents:false]) ssdpSubscribe()
//ssdp request every 25 seconds //ssdp request every 25 seconds
if((refreshCount % 5) == 0) { if((refreshCount % 5) == 0) {
@@ -105,9 +112,7 @@ def devicesDiscovered() {
def motions = getWemoMotions() def motions = getWemoMotions()
def lightSwitches = getWemoLightSwitches() def lightSwitches = getWemoLightSwitches()
def devices = switches + motions + lightSwitches def devices = switches + motions + lightSwitches
def list = [] devices?.collect{ [app.id, it.ssdpUSN].join('.') }
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
} }
def switchesDiscovered() { def switchesDiscovered() {
@@ -175,8 +180,9 @@ def updated() {
def initialize() { def initialize() {
unsubscribe() unsubscribe()
unschedule() unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
ssdpSubscribe()
if (selectedSwitches) if (selectedSwitches)
addSwitches() addSwitches()
@@ -189,7 +195,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() {
@@ -199,7 +205,7 @@ def resubscribe() {
def refresh() { def refresh() {
log.debug "refresh() called" log.debug "refresh() called"
doDeviceSync() doDeviceSync()
refreshDevices() refreshDevices()
} }
@@ -235,14 +241,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 {
@@ -273,9 +279,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"
} }
@@ -304,26 +310,147 @@ 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..."
@@ -335,16 +462,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..."
@@ -459,6 +586,7 @@ 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
@@ -540,4 +668,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() { private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it } return location.hubs*.firmwareVersionString.findAll { it }
} }