Compare commits

..

86 Commits

Author SHA1 Message Date
Oso Technologies 69a6fc4f9e MSA-884: This is a quick update to a previously published device handler to accept the "update" packet that occurs when the zigbee pairs with the hub to stop an exception due to it not being in the map format that other packets are in. 2016-02-17 19:31:22 -08:00
Vinay Rao d82a387c68 Merge pull request #517 from SmartThingsCommunity/master
Rolling up changes to staging from master
2016-02-17 07:47:16 -08: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
Juan Pablo Risso ce26a2941e Merge pull request #506 from juano2310/hue_bu
PROB-537 - Fix error in line 335
2016-02-12 14:34:00 -05:00
Juan Pablo Risso 81fb356a21 PROB-537 - Fix error in line 335 2016-02-12 14:31:04 -05:00
Vinay Rao 20d660e236 Merge pull request #503 from SmartThingsCommunity/master
Merging into staging from master
2016-02-11 22:39:12 -08:00
Vinay Rao aadbcc2eee Merge pull request #501 from SmartThingsCommunity/staging
Rolling down changes from staging to master
2016-02-11 22:11:01 -08:00
Vinay Rao 4a49d4c15d Merge pull request #108 from natec007/MSA-68-1
MSA-68: Spruce Irrigation
2016-02-11 22:06:25 -08:00
Nathan Cauffman 0e3bd5aa74 MSA-68: Spruce Irrigation controller and soil moisture sensors.
Merge in bug fixes and renames to match with new repository layout.

Deleted spruce-controller.groovy from 'Spruce Irrigation'

Deleted spruce-sensor-v1.groovy from 'Spruce Irrigation'

Deleted spruce-scheduler.groovy from 'Spruce Irrigation'

Modifying 'Spruce Irrigation'

Merge in bug fixes and renames to match with new repository layout.
2016-02-11 20:33:19 -08:00
Nathan Cauffman bfd68228bc # This is a combination of 3 commits.
# The first commit's message is:
MSA-68: Spruce Irrigation controller and soil moisture sensors.

Modifying 'Spruce Irrigation'

Updated Spruce Scheduler, better stability, notifications, moisture & season adjustments

Updated device types, start scheduler from device

Code review for ST publication

Review for ST submission

Delete spruce-sensor.groovy
Delete spruce-scheduler-v2-1.groovy

Moisture control adjsut

# This is the 2nd commit message:

Deleted spruce-controller.groovy from 'Spruce Irrigation'
# This is the 3rd commit message:

Deleted spruce-sensor-v1.groovy from 'Spruce Irrigation'
2016-02-11 20:28:09 -08:00
Vinay Rao b1096a67af Merge pull request #500 from SmartThingsCommunity/production
Rolling down changes from production to staging
2016-02-10 14:26:37 -08:00
Ben Boggess ae945d1a15 Merge pull request #499 from boggebe/boggess/convert_hexToSignedInt_to_method
Convert closure to method
2016-02-10 11:16:13 -06:00
boggebe 12220da750 Convert closure to method 2016-02-10 10:57:10 -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
Kris Schaller 208f0d97df Merging staging into production 2016-01-25 13:12:05 -08:00
Kris Schaller e444c8d020 Merging master into staging 2016-01-25 13:11:18 -08:00
Vinay Rao 9f75bbfbde Merge pull request #463 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-20 17:13:25 -08:00
Vinay Rao 996ac27ba2 Merge pull request #462 from SmartThingsCommunity/master
Merging changes into stage
2016-01-20 17:06:44 -08:00
Vinay Rao 16b87c5eda Merge pull request #456 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-19 16:10:44 -08:00
Vinay Rao 05ad96d583 Merge pull request #455 from SmartThingsCommunity/master
Merging changes into stage
2016-01-19 16:08:42 -08:00
Vinay Rao 0a6bb51974 Merge pull request #453 from SmartThingsCommunity/staging
Merging ZigBee RGBW into prod
2016-01-19 15:57:18 -08:00
Vinay Rao 9ab74c3ba6 Merge pull request #452 from SmartThingsCommunity/master
Merging ZigBee RGBW into staging
2016-01-19 15:56:38 -08:00
Tom Manley 8f08a0819c Merge pull request #449 from SmartThingsCommunity/staging
Merge staging into production
2016-01-19 14:25:26 -06:00
Tom Manley d34d1d3615 Merge pull request #448 from SmartThingsCommunity/master
Merge master into staging
2016-01-19 13:46:27 -06:00
Tom Manley 5085d7f184 Merge pull request #433 from SmartThingsCommunity/staging
Merge staging into production
2016-01-13 12:56:38 -06:00
Juan Pablo Risso dc927e0659 Merge pull request #434 from SmartThingsCommunity/master
Master -> Staging
2016-01-13 13:21:51 -05:00
Tom Manley 991637b1c1 Merge pull request #432 from SmartThingsCommunity/master
Merge from master into staging
2016-01-13 10:39:39 -06:00
Juan Pablo Risso 1372d4005a Merge pull request #427 from SmartThingsCommunity/master
Master -> Staging
2016-01-08 16:08:16 -05:00
Yaima 9439efd7b9 Merge pull request #421 from SmartThingsCommunity/master
Merging Ecobee and SmartSense code
2016-01-06 15:34:01 -08:00
Yaima 70b8a042a7 Merge pull request #415 from SmartThingsCommunity/staging
Merging to prod
2016-01-05 16:01:55 -08:00
Yaima 451b7a4923 Merge pull request #414 from SmartThingsCommunity/master
Merging to staging
2016-01-05 16:01:05 -08:00
Kris Schaller 22c810e9df Merging staging into production 2016-01-04 03:43:46 -08:00
Kris Schaller cc80373b89 Merging master into staging 2016-01-04 03:43:30 -08:00
Kris Schaller ea98194abf Merging staging into production 2015-12-28 16:21:37 -08:00
Kris Schaller 94f11c583f Merging master into staging 2015-12-28 16:21:17 -08:00
Kris Schaller 4a6e000cee Merging staging into production 2015-12-28 15:41:51 -08:00
Kris Schaller c3cf4089f4 Merging master into staging 2015-12-28 15:41:29 -08:00
Kris Schaller 71f9f19465 Merging staging into production 2015-12-28 10:31:40 -08:00
Kris Schaller f8317a6d2c Merging master into staging 2015-12-28 10:31:20 -08:00
Kris Schaller 2edda411e2 Merging staging into production 2015-12-21 12:55:20 -08:00
Kris Schaller dd19b6e73f Merging master into staging 2015-12-21 12:55:03 -08:00
Kris Schaller ac74c6126d Merging staging into production 2015-12-15 09:38:16 -08:00
Kris Schaller 80e02416f3 Merging master into staging 2015-12-15 09:38:02 -08:00
Kris Schaller 653f0e28ca Merging staging into production 2015-12-14 17:25:47 -08:00
Kris Schaller 49dc1e96ba Merging master into staging 2015-12-14 17:25:34 -08:00
Kris Schaller 76fcca90d3 Merging staging into production 2015-12-14 17:09:08 -08:00
Kris Schaller c197f4263e Merging master into staging 2015-12-14 17:08:51 -08:00
Kris Schaller 54f976229e Merging staging into production 2015-12-14 12:59:39 -08:00
Kris Schaller 3ba148d5d2 Merging master into staging 2015-12-14 12:59:25 -08:00
Kris Schaller 041733373b Merging staging into production 2015-12-10 15:09:04 -08:00
Kris Schaller b67783a235 Merging master into staging 2015-12-10 15:08:28 -08:00
bflorian b90e2a1982 Merge branch 'staging' into production 2015-12-05 10:26:47 -05:00
bflorian ae444dfe09 Merge branch 'staging' of https://github.com/SmartThingsCommunity/SmartThingsPublic into staging 2015-12-05 10:26:25 -05:00
Donald C. Kirker df55116ac6 Merge pull request #330 from mckeed/prob-398
PROB-398 Fix Homeseer Multi Instance encap parse
2015-12-04 12:38:39 -08:00
Vinay Rao 7d0b3ef796 Merge pull request #333 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-12-02 15:51:30 -08:00
Vinay Rao c3d5f60250 Merge pull request #332 from SmartThingsCommunity/master
Merging changes into stage
2015-12-02 15:49:34 -08:00
Duncan McKee f55ea8b2f8 Fix Homeseer Multi Instance encap parse PROB-398 2015-12-02 11:49:13 -06:00
Juan Pablo Risso 968c9cc647 Merge pull request #317 from SmartThingsCommunity/staging
Staging -> Production
2015-11-26 11:09:51 -05:00
Juan Pablo Risso 3e7ce67ea4 Merge pull request #316 from SmartThingsCommunity/master
Master -> Staging
2015-11-26 11:08:44 -05:00
Vinay Rao 479651a330 Merge pull request #296 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-11-18 14:36:36 -08:00
Vinay Rao 39b00c2a04 Merge pull request #295 from SmartThingsCommunity/master
Merging changes into stage
2015-11-18 14:04:44 -08:00
Vinay Rao 5a52e69911 Merge remote-tracking branch 'origin/staging' into production 2015-11-13 17:10:15 -08:00
Vinay Rao 7f4384cd85 Merge remote-tracking branch 'origin/master' into staging 2015-11-13 17:09:47 -08:00
Vinay Rao 42479a94b4 Merge remote-tracking branch 'origin/staging' into production 2015-11-12 15:30:24 -08:00
Juan Pablo Risso 57e668f1f2 Merge pull request #268 from SmartThingsCommunity/master
Master -> Staging
2015-11-09 16:31:21 -05:00
Warodom Khamphanchai 0321a7f071 Merge pull request #135 from kwarodom/fibaroSmokeSensor
Fibaro Smoke Sensor: initial device type
2015-10-20 10:53:21 -07:00
Vinay Rao 855ed02ffa Merge remote-tracking branch 'origin/staging' into production 2015-10-15 11:55:46 -07:00
Vinay Rao 60fd008d4a Merge remote-tracking branch 'origin/master' into staging 2015-10-15 11:45:09 -07:00
Kris Schaller df764d57c3 Merging staging into production 2015-10-13 16:15:40 -07:00
Kris Schaller a96bb027c8 Merging master into staging 2015-10-13 16:15:28 -07:00
Vinay Rao 6b62f88bb7 Merge remote-tracking branch 'origin/master' into production 2015-10-06 14:13:30 -05:00
Vinay Rao 848bbdcf2b Merge remote-tracking branch 'origin/master' into staging 2015-10-06 13:58:55 -05:00
bflorian 12288accda Merge branch 'master' into staging 2015-10-02 07:21:38 -07:00
Juan Pablo Risso 3533943827 Merge pull request #161 from SmartThingsCommunity/master
Merge Master -> Staging
2015-10-01 11:02:20 -04:00
12 changed files with 454 additions and 366 deletions
@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
} }
def parse(String description) { def parse(String description) {
log.debug description
def description_map = parseDescriptionAsMap(description) def description_map = parseDescriptionAsMap(description)
def event_name = "" def event_name = ""
def measurement_map = [ def measurement_map = [
@@ -129,10 +129,7 @@ def parse(String description) {
zigbeedeviceid: device.zigbeeId, zigbeedeviceid: device.zigbeeId,
created: new Date().time /1000 as int created: new Date().time /1000 as int
] ]
if (description_map.cluster == "0000"){ if (description_map.cluster == "0001"){
/* version number, not used */
} else if (description_map.cluster == "0001"){
/* battery voltage in mV (device needs minimium 2.1v to run) */ /* battery voltage in mV (device needs minimium 2.1v to run) */
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}" log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
event_name = "battery_status" event_name = "battery_status"
@@ -158,6 +155,10 @@ def parse(String description) {
def parseDescriptionAsMap(description) { def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param -> (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":") def nameAndValue = param.split(":")
if(nameAndValue.length == 2){
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}else{
map += []
}
} }
} }
@@ -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)
} }
@@ -19,9 +19,11 @@ 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"
@@ -31,10 +33,13 @@ metadata {
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",
@@ -94,8 +99,11 @@ metadata {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send" state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}% humidity'
}
main "temperature" main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"]) details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh", "humidity"])
} }
preferences { preferences {
@@ -107,8 +115,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() {
@@ -135,14 +141,22 @@ def generateEvent(Map results) {
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=="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"){ } 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=="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 +172,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 +192,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"
} def heatingSetpoint = setpoint.toDouble()
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble() def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint").toDouble()
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint").toDouble()
//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 +221,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"
}
void setCoolingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble() def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
def coolingSetpoint = setpoint def coolingSetpoint = setpoint.toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint").toDouble()
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint").toDouble()
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 +258,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)"
} }
} }
@@ -448,25 +473,21 @@ def auto() {
def fanOn() { def fanOn() {
log.debug "fanOn" log.debug "fanOn"
// parent.setFanMode (this,"on") // parent.setFanMode (this,"on")
} }
def fanAuto() { def fanAuto() {
log.debug "fanAuto" log.debug "fanAuto"
// parent.setFanMode (this,"auto") // parent.setFanMode (this,"auto")
} }
def fanCirculate() { def fanCirculate() {
log.debug "fanCirculate" log.debug "fanCirculate"
// parent.setFanMode (this,"circulate") // parent.setFanMode (this,"circulate")
} }
def fanOff() { def fanOff() {
log.debug "fanOff" log.debug "fanOff"
// parent.setFanMode (this,"off") // parent.setFanMode (this,"off")
} }
def generateSetpointEvent() { def generateSetpointEvent() {
@@ -476,20 +497,41 @@ 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 +541,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 +552,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 +589,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 +625,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 +652,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 +679,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 +696,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 +713,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 +726,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()
}
@@ -80,19 +80,12 @@ def parse(String description) {
if (cmd) { if (cmd) {
result = zwaveEvent(cmd) result = zwaveEvent(cmd)
} }
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}" log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
return result return result
} }
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) { def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
def encapsulated = null def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
if (cmd.respondsTo("encapsulatedCommand")) {
encapsulated = cmd.encapsulatedCommand()
} else {
def hex1 = { n -> String.format("%02X", n) }
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
}
return encapsulated ? zwaveEvent(encapsulated) : null return encapsulated ? zwaveEvent(encapsulated) : null
} }
@@ -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"
} }
@@ -487,11 +487,6 @@ def enrollResponse() {
} }
private Map parseAxis(String description) { private Map parseAxis(String description) {
def hexToSignedInt = { hexVal ->
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def z = hexToSignedInt(description[0..3]) def z = hexToSignedInt(description[0..3])
def y = hexToSignedInt(description[10..13]) def y = hexToSignedInt(description[10..13])
def x = hexToSignedInt(description[20..23]) def x = hexToSignedInt(description[20..23])
@@ -518,6 +513,11 @@ private Map parseAxis(String description) {
getXyzResult(xyzResults, description) getXyzResult(xyzResults, description)
} }
private hexToSignedInt(hexVal) {
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def garageEvent(zValue) { def garageEvent(zValue) {
def absValue = zValue.abs() def absValue = zValue.abs()
def contactValue = null def contactValue = null
@@ -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)}"
@@ -259,7 +258,7 @@ def getEcobeeThermostats() {
} else { } else {
log.debug "http status: ${resp.status}" log.debug "http status: ${resp.status}"
//refresh the auth token //refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) { if (resp.data.status.code == 14) {
log.debug "Storing the failed action to try later" log.debug "Storing the failed action to try later"
atomicState.action = "getEcobeeThermostats" atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
@@ -317,7 +316,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 +327,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"
@@ -357,18 +356,14 @@ def initialize() {
//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 +384,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 +398,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,20 +408,41 @@ 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
] ]
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
@@ -450,9 +453,8 @@ def pollChildren(child = null) {
log.error "polling children & got http status ${resp.status}" log.error "polling children & got http status ${resp.status}"
//refresh the auth token //refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) { if (resp.data.status.code == 14) {
log.debug "Storing the failed action to try later" atomicState.action = "pollChildren"
atomicState.action = "pollChildren";
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken()
} }
@@ -463,7 +465,6 @@ def pollChildren(child = null) {
} }
} catch(Exception e) { } catch(Exception e) {
log.debug "___exception polling children: " + e log.debug "___exception polling children: " + e
// debugEventFromParent(child, "___exception polling children: " + e)
refreshAuthToken() refreshAuthToken()
} }
return result return result
@@ -476,18 +477,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 +510,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 +536,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 +553,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 +571,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}")
} }
} }
} }
@@ -641,7 +636,6 @@ private refreshAuthToken() {
"${atomicState.action}"() "${atomicState.action}"()
//remove saved action
atomicState.action = "" atomicState.action = ""
} }
@@ -651,7 +645,7 @@ private refreshAuthToken() {
log.debug "refresh failed ${resp.status} : ${resp.status.code}" log.debug "refresh failed ${resp.status} : ${resp.status.code}"
} }
} }
} catch(Exception e) { } catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc. if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
@@ -672,13 +666,9 @@ private refreshAuthToken() {
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
} }
@@ -687,27 +677,16 @@ 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) 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 +703,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}"
@@ -741,8 +718,7 @@ def sendJson(child = null, String jsonBody) {
debugEvent ("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 //refresh the auth token
if (resp.status == 500 && resp.status.code == 14) { if (resp.status.code == 14) {
//log.debug "Storing the failed action to try later"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
debugEvent ("Refreshing OAUTH Token") debugEvent ("Refreshing OAUTH Token")
refreshAuthToken() refreshAuthToken()
@@ -816,3 +792,15 @@ def sendActivityFeeds(notificationMessage) {
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()
}
@@ -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)")
} }
} }
@@ -35,24 +35,12 @@ 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 { } else {
return bridgeDiscovery() return bridgeDiscovery()
} }
} else {
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
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"
}
}
}
} }
def bridgeDiscovery(params=[:]) def bridgeDiscovery(params=[:])
@@ -326,6 +314,7 @@ def addBulbs() {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} }
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else { } else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed" log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
} }
@@ -333,8 +322,8 @@ def addBulbs() {
//backwards compatable //backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
}
d.refresh() d.refresh()
}
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) { if (bulbs instanceof java.util.Map) {
@@ -764,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 }
} }
@@ -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 }
if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"]) d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni" log.trace "created ${d.displayName} with id $dni"
poll() poll()
}
} else { } else {
log.trace "found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }
@@ -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=모드 선택
@@ -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'
if (!phone || pushAndPhone != "No") { options.method = 'push'
log.debug "sending push" //sendPush(msg)
sendPush(msg)
} }
if (phone) { if (phone) {
log.debug "sending SMS" options.phone = phone
sendSms(phone, msg) log.debug 'sending SMS'
//sendSms(phone, msg)
} }
sendNotification(msg, options)
} }
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
} }
} }