Compare commits

...

70 Commits

Author SHA1 Message Date
Aquilino Wong
741dd36689 MSA-1476: Centralite HA thermostat. Works perfectly, able to see temperature, control it, fan, heat, on and off,hold. I didnt make this code, i want this to be published for everybody who has twc or comcast intelligent home. They use this type of device and you can convert them to smartthings. 2016-09-16 16:31:28 -05:00
Juan Pablo Risso
a6c7ab49b6 SSVD-2737 - Temperature Unit (#1247) 2016-09-14 09:43:26 -04:00
Vinay Rao
629d768575 Merge pull request #1243 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-13 13:44:38 -07:00
Andrew Bresee
4523498dab Add parens to unsubscribe method (#1231) 2016-09-13 13:23:55 -04:00
Jason Terhune
e89e45e000 Another circle fix: dependencies task needs artifactory creds too. 2016-09-13 08:47:22 -05:00
Jason Terhune
78ec280e83 Fix circle build. 2016-09-13 08:37:20 -05:00
Jason Terhune-Wold
1f144d36e4 Merge pull request #1221 from jterhune/gradle-compile
Configure gradle to compile devicetypes and smartapps.
2016-09-13 08:30:57 -05:00
Vinay Rao
f5bd580c9e Merge pull request #1240 from juano2310/staging
SSVD-2733 -  Typo fix
2016-09-12 18:00:11 -07:00
juano2310
d5329dbde3 SSVD-2733 - Typo fix 2016-09-12 20:58:36 -04:00
Vinay Rao
48818cfb06 Merge pull request #1239 from larsfinander/CHF-348_philips_hue_staging
CHF-348 Philips Hue: Change offline time to 16 min
2016-09-12 16:23:41 -07:00
Lars Finander
079919260b CHF-348 Philips Hue: Change offline time to 16 min 2016-09-12 17:19:14 -06:00
Vinay Rao
570922e2ac Merge pull request #1238 from jackchi/healthcheck-5min-reporting
[CHF-348] Set 10 VD required devices to report in at 5 minutes
2016-09-12 15:52:29 -07:00
Lars Finander
53ed9b4d2b Merge pull request #1237 from larsfinander/add_hue_offline_events_staging
Add offline events to Philips Hue
2016-09-12 16:49:57 -06:00
jackchi
7149a81c85 [CHF-348] Set 10 VD required devices to report in at 5 minutes 2016-09-12 14:51:57 -07:00
Vinay Rao
30274f0cd7 Merge pull request #914 from jazzslider/motion-labels
Newer Fibaro motion sensor handler needs motion state labels
2016-09-12 13:26:28 -07:00
Jack Chi
8869cd3af0 Merge pull request #1232 from jackchi/healthcheck-all
[CHF-201] Removing DTH workaround now that all events go to Kafka
2016-09-12 10:46:12 -07:00
Jack Chi
fb0caa6446 Merge pull request #1233 from jackchi/healthcheck-removehack
[CHF-201] Removing DTH workaround now that all events go to Kafka
2016-09-12 08:24:10 -07:00
Lars Finander
3d05d42cb8 Add offline events to Philips Hue
-Handles case when bridge goes offline
-Handles case when indiviudual lights go offline
2016-09-11 22:56:24 -06:00
Vinay Rao
3184615e87 Merge pull request #1235 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-09 16:50:50 -07:00
Vinay Rao
0f70362e0a Merge pull request #1234 from larsfinander/DVCSMP-2024_OSRAM_Hue_incorrect_staging
DVCSMP-2024 OSRAM: Hue is incorrectly set to be 0-360
2016-09-09 16:49:31 -07:00
Lars Finander
bc817f8530 DVCSMP-2024 OSRAM: Hue is incorrectly set to be 0-360
-Hue changed to handle 0-100 according to SmartThings API
2016-09-09 17:41:46 -06:00
jackchi
01b8399893 [CHF-201] Removing DTH workaround now that all events go to Kafka 2016-09-09 14:58:03 -07:00
jackchi
81318bafac [CHF-201] Removing DTH workaround now that all events go to Kafka 2016-09-09 14:48:19 -07:00
Lars Finander
60c2006bfc Merge pull request #1230 from larsfinander/SSVD-2798_Philips_Hue_discovery_issue
SSVD-2798 Philips Hue: Light discovery UI issue
2016-09-09 11:01:40 -06:00
Lars Finander
1e4f1223e7 SSVD-2798 Philips Hue: Light discovery UI issue
-Keep checked lights in "lights to add list" when page refreshes
2016-09-09 10:50:23 -06:00
Jason Terhune
b78bce55b2 Configure gradle to compile devicetypes and smartapps. 2016-09-09 09:41:11 -05:00
Vinay Rao
01593c3973 Merge pull request #1229 from SmartThingsCommunity/staging
Rolling down hotfix to master
2016-09-08 19:34:52 -07:00
Vinay Rao
6862785d6c Merge pull request #1228 from SmartThingsCommunity/production
Rolling down hotfix to staging
2016-09-08 19:33:26 -07:00
Amol Mundayoor
763d7411e2 Merge pull request #1227 from munds/netatmo
Removing debug logs for netatmo
2016-09-08 11:22:42 -07:00
Amol Mundayoor
c703543f36 Commenting out log.debug instead of removing them 2016-09-08 11:20:27 -07:00
dsainteclaire
5db6ecda3e Merge pull request #1226 from dsainteclaire/DVCSMP-1959-remove-logging-of-phone-numbers
DVCSMP-1959 removed logging of device and phone information due to security concerns
2016-09-08 11:10:22 -07:00
Amol Mundayoor
43b836f413 Removing debug logs for netatmo 2016-09-08 11:07:31 -07:00
David Sainte-Claire
006b5e7bea removed logging of device and phone information due to security concerns 2016-09-08 10:54:04 -07:00
Rohan Desai
62c8c19805 Merge pull request #1225 from rohandesai/dynamic-execution-fix
DVCSMP-1959: fixes for dynamic execution (Curb-control)
2016-09-08 10:47:55 -07:00
Rohan Desai
48e9a4bd6a removed semi colons 2016-09-08 10:46:40 -07:00
dsainteclaire
07a4c0decc Merge pull request #1223 from dsainteclaire/DVCSMP-1959-remove-sensitive-information-withings
DVCSMP-1959 removed logging of sensitive access tokens from smartapp
2016-09-08 10:41:56 -07:00
Andrew Bresee
6aa09bb052 Change log statement to not log personal phone number (#1224) 2016-09-08 13:40:35 -04:00
Rohan Desai
2f889de11a fixes for dynamic execution 2016-09-08 10:36:28 -07:00
David Sainte-Claire
5584020e96 removed logging of sensitive access tokens from smartapp 2016-09-08 10:31:24 -07:00
Jim Anderson
4ef2e694c2 Merge pull request #1180 from jimmyjames/ecobee-async-http-polling
[DVCSMP-1979] Use async http for polling and refresh tokens.
2016-09-08 12:13:53 -05:00
Jim Anderson
826993cc45 [DVCSMP-1979] Use async http for polling and refresh tokens. 2016-09-08 12:11:21 -05:00
Luke Bredeson
ae3306928b Merge pull request #1220 from lbredeso/fix-smart-security
DVCSMP-2020: smart-security app contains invalid code
2016-09-08 10:14:01 -05:00
Luke Bredeson
8777ec5f6d DVCSMP-2020: smart-security app contains invalid code 2016-09-08 10:06:38 -05:00
Andrew Bresee
91eb59a10d DVCSMP-1959: Replace log logging personal phone number (#1217)
* Replace log logging personal phone number

* Removed commented out log
2016-09-08 07:45:30 -04:00
Andrew Bresee
324ac13afb DVCSMP-1959: Unsubscribe from a pointless method (#1215)
* Unsubscribe from a pointless method

* Remove subscription to pointless method
2016-09-07 18:07:01 -04:00
Andrew Bresee
878eb66b8b Remove log logging personal information about a device (#1214) 2016-09-07 16:30:35 -04:00
Rohan Desai
8dfc270c2d Merge pull request #1213 from rohandesai/log-security-fixes
DVCSMP-1959 security fixes for SAs
2016-09-07 12:58:25 -07:00
Rohan Desai
614573a15c security fixes for SAs 2016-09-07 11:28:08 -07:00
dsainteclaire
9c7b0875ba Merge pull request #1211 from dsainteclaire/DVCSMP-1959-remove-sensitive-information-bon-voyage
DVCSMP-1959 removed log messages from smartapp that may print sensitive information
2016-09-07 11:24:22 -07:00
Andrew Bresee
7568cbf781 DVCSMP-1959: Security review - removing potentially confidential log statements (#1210)
* Commented out log statement logging users access token

* Commented out log.info logging potentially confidential device information

* Remove log logging potentially confidential information on the app
2016-09-07 14:11:20 -04:00
Vinay Rao
1858c280a5 Merge pull request #1212 from workingmonk/bug/negative_battery_1
SSVD-2771 fix to avoid negative values
2016-09-07 11:09:02 -07:00
Vinay Rao
6b7d0968f6 chaning min battery percentage to 1 2016-09-07 11:07:30 -07:00
David Sainte-Claire
159d3acf4f removed log messages from smartapp that may print sensitive information 2016-09-07 10:56:25 -07:00
Vinay Rao
e8101630a3 Merge pull request #1209 from workingmonk/bug/negative_battery
SSVD-2771 fix to avoid negative values
2016-09-07 10:49:40 -07:00
Vinay Rao
f5ba78b221 fix to avoid negative values 2016-09-07 10:46:12 -07:00
tslagle13
19b8a7eeb9 Merge pull request #1167 from CosmicPuppy/CosmicPuppy-Netatmo-CapabilitySensor
MSA-1468 - To Netatmo DTHs, added Capability "Sensor"
2016-09-07 10:15:42 -07:00
twack
1b37d649a5 Merge pull request #1207 from twack/20160907_update_its_too_hot
(DVCSMP-1959) 20160907_update_its_to_hot_for_security
2016-09-07 09:22:09 -07:00
twack
dedb0f8465 Update its-too-hot.groovy 2016-09-07 09:20:13 -07:00
Juan Pablo Risso
06acc13575 DVCSMP-1959 - Remove sensitive information from logs (#1206) 2016-09-07 12:14:58 -04:00
twack
c051d719cc 20160907_update_its_to_hot_for_security 2016-09-07 09:14:19 -07:00
Vinay Rao
1e54b93b0c Merge pull request #1203 from SmartThingsCommunity/master
Rolling up master to staging
2016-09-06 17:15:57 -07:00
Vinay Rao
bac37f9ca2 Merge pull request #1202 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-06 17:08:39 -07:00
Vinay Rao
f0ecb65c09 Merge pull request #1201 from SmartThingsCommunity/revert-1197-revert_WWST-40_staging
Revert "Revert "WWST-40 Philips Hue: Implement device watch""
2016-09-06 17:07:33 -07:00
Vinay Rao
1c0ddd2571 Revert "Revert "WWST-40 Philips Hue: Implement device watch"" 2016-09-06 17:07:14 -07:00
Vinay Rao
b7e0cbda09 Merge pull request #1200 from SmartThingsCommunity/revert-1194-revert-latest-ecobee-changes
Revert "revert changes for DVCSMP-1980 and SSVD-2534 on staging"
2016-09-06 17:07:06 -07:00
Vinay Rao
f80e094bd9 Revert "revert changes for DVCSMP-1980 and SSVD-2534 on staging" 2016-09-06 17:06:38 -07:00
Juan Pablo Risso
383f72580a SSVD-2738 - SSVD-2734 - SSVD-2733 (#1187)
* SSVD-2738 - Count Down Bug

* SSVD-2734 - Copy text

* SSVD-2733 - Add Unit
2016-09-06 11:39:18 -04:00
Vinay Rao
090a306939 Merge pull request #1184 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-08-31 16:05:08 -07:00
CosmicPuppy
5e6b4f74e0 To Netatmo DTHs, added Capability "Sensor" per http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html?highlight=sensor%20actuator#actuator-and-sensor.
There are some SmartApps out there using the "Actuator" and "Sensor" Capabilities and this Device doesn't show up for them (e.g., SmartTiles V6).
2016-08-29 13:55:19 -07:00
Adam Jensen
22185c5440 Added labels to the motion attribute in the main multiattributetile 2016-05-24 08:18:51 -05:00
54 changed files with 1596 additions and 762 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
username smartThingsArtifactoryUserName username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword password smartThingsArtifactoryPassword
} }
url "http://artifactory.smartthings.com/libs-release-local" url "https://artifactory.smartthings.com/libs-release-local"
} }
} }
} }
@@ -27,9 +27,37 @@ buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
maven {
credentials {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "https://artifactory.smartthings.com/libs-release-local"
}
}
sourceSets {
devicetypes {
groovy {
srcDirs = ['devicetypes']
}
}
smartapps {
groovy {
srcDirs = ['smartapps']
}
}
} }
dependencies { dependencies {
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
smartappsCompile 'smartthings:appengine-common:0.1.8'
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
smartappsCompile 'org.grails:grails-web:2.3.11'
smartappsCompile 'org.json:json:20140107'
} }
slackSendMessage { slackSendMessage {

View File

@@ -5,7 +5,9 @@ machine:
dependencies: dependencies:
override: override:
- echo "Nothing to do." - ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
post:
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
test: test:
override: override:

View File

@@ -15,6 +15,7 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"

View File

@@ -15,6 +15,7 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"

View File

@@ -15,6 +15,7 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"
} }

View File

@@ -15,6 +15,8 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
attribute "rain", "number" attribute "rain", "number"
attribute "rainSumHour", "number" attribute "rainSumHour", "number"
attribute "rainSumDay", "number" attribute "rainSumDay", "number"

View File

@@ -33,8 +33,8 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.motion", key:"PRIMARY_CONTROL") { tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821") attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e") attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
} }
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") { tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {

View File

@@ -67,12 +67,6 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getEvent(description)
if (resultMap) { if (resultMap) {
sendEvent(resultMap) sendEvent(resultMap)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
} }
else { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -96,15 +90,7 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.levelRefresh() return zigbee.levelRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -117,6 +103,8 @@ def poll() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -67,6 +67,6 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild() parent.poll()
} }

View File

@@ -133,7 +133,7 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild() parent.poll()
} }
def generateEvent(Map results) { def generateEvent(Map results) {
@@ -152,11 +152,11 @@ def generateEvent(Map results) {
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
isChange = isTemperatureStateChange(device, name, value.toString()) isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed] event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") { } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false] event << [value: sendValue, unit: temperatureScale, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString()) isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false] event << [value: value.toString(), isStateChange: isChange, displayed: false]
@@ -234,9 +234,9 @@ void setHeatingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
@@ -271,9 +271,9 @@ void setCoolingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}" log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
@@ -287,14 +287,14 @@ void resumeProgram() {
log.debug "resumeProgram() is called" log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.resumeProgram(this, deviceId)) { if (parent.resumeProgram(deviceId)) {
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
runIn(5, "poll") runIn(5, "poll")
log.debug "resumeProgram() is done" log.debug "resumeProgram() is done"
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true) sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
} else { } else {
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false) sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)" log.error "Error resumeProgram() check parent.resumeProgram(deviceId)"
} }
} }
@@ -406,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
def off() { def off() {
log.debug "off" log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"off", deviceId)) if (parent.setMode ("off", deviceId))
generateModeEvent("off") generateModeEvent("off")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -420,7 +420,7 @@ def off() {
def heat() { def heat() {
log.debug "heat" log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"heat", deviceId)) if (parent.setMode ("heat", deviceId))
generateModeEvent("heat") generateModeEvent("heat")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -438,7 +438,7 @@ def emergencyHeat() {
def auxHeatOnly() { def auxHeatOnly() {
log.debug "auxHeatOnly" log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"auxHeatOnly", deviceId)) if (parent.setMode ("auxHeatOnly", deviceId))
generateModeEvent("auxHeatOnly") generateModeEvent("auxHeatOnly")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -452,7 +452,7 @@ def auxHeatOnly() {
def cool() { def cool() {
log.debug "cool" log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"cool", deviceId)) if (parent.setMode ("cool", deviceId))
generateModeEvent("cool") generateModeEvent("cool")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -466,7 +466,7 @@ def cool() {
def auto() { def auto() {
log.debug "auto" log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"auto", deviceId)) if (parent.setMode ("auto", deviceId))
generateModeEvent("auto") generateModeEvent("auto")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -489,7 +489,7 @@ def fanOn() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode) generateFanModeEvent(fanMode)
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -510,7 +510,7 @@ def fanAuto() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode) generateFanModeEvent(fanMode)
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
@@ -556,12 +556,12 @@ def generateSetpointEvent() {
if (mode == "heat") { if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint ) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
} }
else if (mode == "cool") { else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint) sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
} else if (mode == "auto") { } else if (mode == "auto") {
@@ -573,7 +573,7 @@ def generateSetpointEvent() {
} else if (mode == "auxHeatOnly") { } else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint) sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
} }
@@ -608,7 +608,7 @@ void raiseSetpoint() {
targetvalue = maxCoolingSetpoint targetvalue = maxCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
log.info "In mode $mode raiseSetpoint() to $targetvalue" log.info "In mode $mode raiseSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -644,7 +644,7 @@ void lowerSetpoint() {
targetvalue = minCoolingSetpoint targetvalue = minCoolingSetpoint
} }
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
log.info "In mode $mode lowerSetpoint() to $targetvalue" log.info "In mode $mode lowerSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -690,10 +690,10 @@ void alterSetpoint(temp) {
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false) sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint) sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint) sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}" log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else { } else {
log.error "Error alterSetpoint()" log.error "Error alterSetpoint()"

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,7 +91,7 @@ def parse(String description) {
if (descMap.cluster == "0300") { if (descMap.cluster == "0300") {
if(descMap.attrId == "0000"){ //Hue Attribute if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360) def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue" log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false) sendEvent(name: "hue", value: hueValue, displayed:false)
} }
@@ -203,7 +203,7 @@ def setLevel(value) {
//input Hue Integer values; returns color name for saturation 100% //input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){ private getColorName(hueValue){
if(hueValue>360 || hueValue<0) if(hueValue>100 || hueValue<0)
return return
hueValue = Math.round(hueValue / 100 * 360) hueValue = Math.round(hueValue / 100 * 360)

View File

@@ -118,7 +118,7 @@ def parse(String description) {
} }
} }
else if(descMap.attrId == "0000"){ //Hue Attribute else if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360) def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue" log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false) sendEvent(name: "hue", value: hueValue, displayed:false)
} }
@@ -274,7 +274,7 @@ private getGenericName(value){
//input Hue Integer values; returns color name for saturation 100% //input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){ private getColorName(hueValue){
if(hueValue>360 || hueValue<0) if(hueValue>100 || hueValue<0)
return return
hueValue = Math.round(hueValue / 100 * 360) hueValue = Math.round(hueValue / 100 * 360)

View File

@@ -101,12 +101,6 @@ def parse(String description) {
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
} }
} }
else { else {
@@ -126,15 +120,7 @@ def on() {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh() return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -142,8 +128,10 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
zigbee.onOffConfig() + powerConfig() + refresh() sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
} }
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) //power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -101,13 +101,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -233,6 +226,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -283,15 +278,7 @@ private Map getMoistureResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -305,23 +292,19 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
] ]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -105,13 +105,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -248,6 +241,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -294,15 +289,7 @@ private Map getMotionResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -316,24 +303,19 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
return configCmds + refresh() // send refresh cmds as part of config // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -206,6 +206,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -127,13 +127,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
@@ -313,6 +306,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -376,15 +371,7 @@ private getAccelerationResult(numValue) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -414,13 +401,16 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = enrollResponse() + def configCmds = enrollResponse() +
zigbee.batteryConfig() + zigbee.batteryConfig() +
zigbee.temperatureConfig() + zigbee.temperatureConfig(30, 300) +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +

View File

@@ -206,6 +206,8 @@ def getTemperature(value) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -92,13 +92,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -207,6 +200,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -246,15 +241,7 @@ private Map getContactResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -268,23 +255,19 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
] ]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -83,13 +83,6 @@ def parse(String description) {
map = parseCustomMessage(description) map = parseCustomMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
return map ? createEvent(map) : null return map ? createEvent(map) : null
} }
@@ -214,6 +207,8 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -251,14 +246,7 @@ private Map getHumidityResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() def refresh()
@@ -276,23 +264,19 @@ def refresh()
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
def configCmds = [ def humidityConfigCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500", "zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500" "send 0x${device.deviceNetworkId} 1 1", "delay 500"
] ]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
private hex(value) { private hex(value) {

View File

@@ -0,0 +1,630 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Author: SmartThings
* Date: 2013-12-02
*/
metadata {
definition (name: "ZigBee CentraLite Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Temperature Measurement"
capability "Thermostat"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Polling"
capability "Battery"
// Custom commands
command "raiseHeatLevel"
command "lowHeatLevel"
command "raiseCoolLevel"
command "lowCoolLevel"
command "setTemperature"
command "setThermostatHoldMode"
command "getPowerSource"
attribute "temperatureScale", "string"
attribute "thermostatHoldMode", "string"
attribute "powerSource", "string"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0020,0201,0202,0204,0B05", outClusters: "000A, 0019"
}
// simulator metadata
simulator { }
tiles(scale: 2) {
multiAttributeTile(name:"summary", type: "thermostat", width: 6, height: 4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temperature", label:'${currentValue}°', unit:"dF",
backgroundColors:[
// Celsius Color Range
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 33, color: "#d04e00"],
[value: 36, color: "#bc2323"],
// Fahrenheit Color Range
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 65, color: "#99ff33"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 92, color: "#d04e00"],
[value: 96, color: "#bc2323"]
])
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.powerSource", key: "SECONDARY_CONTROL") {
attributeState("default", label:'PowerSrc: ${currentValue}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"",
backgroundColors:[
// Celsius Color Range
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 33, color: "#d04e00"],
[value: 36, color: "#bc2323"],
// Fahrenheit Color Range
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 65, color: "#99ff33"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 92, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("mode", "device.thermostatMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "off", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heating-cooling-off"
state "cool", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.cool", backgroundColor: '#99ddff'
state "heat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heat", backgroundColor: '#fd631c'
state "emergencyHeat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.emergency-heat", backgroundColor: '#E11102'
//state "auto", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.auto", backgroundColor: '#ffff00'
}
standardTile("fanMode", "device.thermostatFanMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "fanAuto", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-auto", backgroundColor: '#ffff00'
state "fanOn", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-on", backgroundColor: '#ffff00'
}
standardTile("holdMode", "device.thermostatHoldMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "holdOff", label:'Hold Off', action:"setThermostatHoldMode", icon: "st.Lighting.light13", backgroundColor:"#ffffff", nextState:"holdOff"
state "holdOn", label:'Hold On', action:"setThermostatHoldMode", icon: "st.Lighting.light11", backgroundColor:"#ffb3ff", nextState:"holdOn"
}
standardTile("raiseHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "raiseHeatLevel", label:' ', action:"raiseHeatLevel", icon:"st.thermostat.thermostat-up"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° Heat', unit:"", backgroundColor:"#fd631c"
}
standardTile("lowHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "lowHeatLevel", label:' ', action:"lowHeatLevel", icon:"st.thermostat.thermostat-down"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
}
standardTile("raiseCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "raiseCoolLevel", label:' ', action:"raiseCoolLevel", icon:"st.thermostat.thermostat-up"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", , height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° Cool', unit:"", backgroundColor:"#66ccff"
}
standardTile("lowCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "lowCoolLevel", label:' ', action:"lowCoolLevel", icon:"st.thermostat.thermostat-down"
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#003CEC"
}
standardTile("refresh", "device.temperature", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% Battery', unit:""
}
main(["temperature", "summary"])
details(["summary", "holdMode", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "battery", "refresh"])
}
}
def setTemperature(setpoint) {
log.debug "setTemperature() called with setpoint ${setpoint}. "
log.debug "Current temperature: ${device.currentValue("temperature")}. Heat Setpoint: ${device.currentValue("heatingSetpoint")}. Cool Setpoint: ${device.currentValue("coolingSetpoint")}"
def mode = device.currentValue("thermostatMode")
def midpoint
def targetvalue
if (mode == "off") {
log.warn "setTemperature(): this mode: $mode does not allow raiseSetpoint"
return
}
def currentTemp = device.currentValue("temperature")
def deltaTemp = setpoint - currentTemp
log.debug "deltaTemp = ${deltaTemp}"
if (mode == "heat") {
// Change the heat
log.debug "setTemperature(): change the heat temp"
if (deltaTemp < 0) {
lowHeatLevel()
} else if (deltaTemp > 0) {
raiseHeatLevel()
}
} else if (mode == "cool") {
// Change the cool
log.debug "setTemperature(): change the cool temp"
if (deltaTemp < 0) {
lowCoolLevel()
} else if (deltaTemp > 0) {
raiseCoolLevel()
}
}
}
def raiseHeatLevel(){
def mode = device.currentValue("thermostatMode")
if (mode == "off") {
log.warn "raiseHeatLevel(): this mode: $mode does not allow raiseHeatLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("heatingSetpoint")
int nextLevel = currentLevel.toInteger() + 1
if( nextLevel > maxTemp){
nextLevel = maxTemp
}
log.debug "Setting heat set point up to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
def lowHeatLevel(){
if (mode == "off") {
log.warn "lowHeatLevel(): this mode: $mode does not allow lowHeatLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("heatingSetpoint")
int nextLevel = currentLevel.toInteger() - 1
if( nextLevel < minTemp){
nextLevel = minTemp
}
log.debug "Setting heat set point down to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
def raiseCoolLevel(){
if (mode == "off") {
log.warn "raiseCoolLevel(): this mode: $mode does not allow raiseCoolLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("coolingSetpoint")
int nextLevel = currentLevel.toInteger() + 1
if( nextLevel > maxTemp){
nextLevel = maxTemp
}
log.debug "Setting cool set point up to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
def lowCoolLevel(){
if (mode == "off") {
log.warn "lowCoolLevel(): this mode: $mode does not allow lowCoolLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("coolingSetpoint")
int nextLevel = currentLevel.toInteger() - 1
if( nextLevel < minTemp){
nextLevel = minTemp
}
log.debug "Setting cool set point down to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
log.debug "MODE"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
log.debug "FAN MODE"
map.name = "thermostatFanMode"
map.value = getFanModeMap()[descMap.value]
} else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
log.debug "BATTERY"
map.name = "battery"
map.value = getBatteryLevel(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "001e") {
log.debug "RUN MODE"
map.name = "thermostatRunMode"
//map.value = getRunModeMap()[descMap.value]
map.value = getThermostatRunMode(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0023") {
log.debug "HOLD MODE"
map.name = "thermostatHoldMode"
map.value = getHoldModeMap()[descMap.value]
} else if (descMap.cluster == "0000" && descMap.attrId == "0007") {
log.debug "Power Source"
map.name = "powerSource"
map.value = getPowerSource()[descMap.value]
}
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getModeMap() { [
"00":"off",
//"01":"auto",
"03":"cool",
"04":"heat",
"05":"emergencyHeat"
]}
def getHoldModeMap() { [
"00":"holdOff",
"01":"holdOn",
]}
def getPowerSource() { [
"01":"24VAC",
"03":"Battery",
]}
def getFanModeMap() { [
"04":"fanOn",
"05":"fanAuto"
]}
def getThermostatRunMode(value) {
if (value != null) {
def RunModeValue = Integer.parseInt(value, 16)
log.debug "Thermostat RunMode: ${RunModeValue} "
}
}
def getTemperature(value) {
if (value != null) {
def celsius = Integer.parseInt(value, 16) / 100
if (getTemperatureScale() == "C") {
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
}
}
}
def setHeatingSetpoint(degrees) {
if (degrees != null) {
def temperatureScale = getTemperatureScale()
def degreesInteger = Math.round(degrees)
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
}
}
def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}
}
def modes() {
["off", "cool", "heat", "emergencyHeat"]
}
def setThermostatMode() {
log.debug "switching thermostatMode"
def currentMode = device.currentState("thermostatMode")?.value
def modeOrder = modes()
log.debug "modeOrder: ${modeOrder}"
def index = modeOrder.indexOf(currentMode)
log.debug "index: ${index}"
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
log.debug "switching mode from $currentMode to $next"
"$next"()
}
def setThermostatFanMode() {
log.debug "Switching fan mode"
def currentFanMode = device.currentState("thermostatFanMode")?.value
log.debug "switching fan from current mode: $currentFanMode"
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = fanOn()
break
case "fanOn":
returnCommand = fanAuto()
break
}
if(!currentFanMode) { returnCommand = fanAuto() }
returnCommand
}
def setThermostatHoldMode() {
log.debug "Switching Hold mode"
def currentHoldMode = device.currentState("thermostatHoldMode")?.value
log.debug "switching thermostat from current mode: $currentHoldMode"
def returnCommand
switch (currentHoldMode) {
case "holdOff":
returnCommand = holdOn()
break
case "holdOn":
returnCommand = holdOff()
break
}
if(!currentHoldMode) { returnCommand = holdOff() }
returnCommand
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
"$value"()
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
"$value"()
}
def setThermostatHoldMode(String value) {
log.debug "setThermostatHoldMode({$value})"
"$value"()
}
def off() {
log.debug "off"
sendEvent("name":"thermostatMode", "value":"off")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {00}"
}
def cool() {
log.debug "cool"
sendEvent("name":"thermostatMode", "value":"cool")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {03}"
}
def heat() {
log.debug "heat"
sendEvent("name":"thermostatMode", "value":"heat")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {04}"
}
def emergencyHeat() {
log.debug "emergencyHeat"
sendEvent("name":"thermostatMode", "value":"emergencyHeat")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {05}"
}
def on() {
fanOn()
}
def fanOn() {
log.debug "fanOn"
sendEvent("name":"thermostatFanMode", "value":"fanOn")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {04}"
}
def auto() {
fanAuto()
}
def fanAuto() {
log.debug "fanAuto"
sendEvent("name":"thermostatFanMode", "value":"fanAuto")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {05}"
}
def holdOn() {
log.debug "Set Hold On for thermostat"
sendEvent("name":"thermostatHoldMode", "value":"holdOn")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {01}"
}
def holdOff() {
log.debug "Set Hold Off for thermostat"
sendEvent("name":"thermostatHoldMode", "value":"holdOff")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {00}"
}
// Commment out below if no C-wire since it will kill the batteries.
def poll() {
// log.debug "Executing 'poll'"
refresh()
}
def configure() {
log.debug "binding to Thermostat and Fan Control cluster"
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x000 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x201 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x202 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 3600 86400 {01}", "delay 100", //battery report request
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
def refresh()
{
log.debug "refresh called"
[
"st rattr 0x${device.deviceNetworkId} 1 0x000 0x07", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x11", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x12", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1C", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1E", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x23", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x001 0x20", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x202 0"
] + configure()
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}

View File

@@ -54,12 +54,6 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {} if (event.name=="level" && event.value==0) {}
else { else {
sendEvent(event) sendEvent(event)
@@ -86,15 +80,7 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh() return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -103,7 +89,8 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -85,12 +85,6 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
log.debug event log.debug event
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {} if (event.name=="level" && event.value==0) {}
else { else {
if (event.name=="colorTemperature") { if (event.name=="colorTemperature") {
@@ -105,7 +99,7 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360) def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed") sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
} }
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
@@ -130,15 +124,7 @@ def off() {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh() return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -147,9 +133,10 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
} }
def setColorTemperature(value) { def setColorTemperature(value) {

View File

@@ -74,12 +74,6 @@ def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {} if (event.name=="level" && event.value==0) {}
else { else {
if (event.name=="colorTemperature") { if (event.name=="colorTemperature") {
@@ -110,15 +104,7 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh() return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
} }
def refresh() { def refresh() {
@@ -127,9 +113,10 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }
def setColorTemperature(value) { def setColorTemperature(value) {

View File

@@ -65,7 +65,16 @@ void updateSwitch() {
private void updateAll(devices) { private void updateAll(devices) {
def command = request.JSON?.command def command = request.JSON?.command
if (command) { if (command) {
devices."$command"() switch(command) {
case "on":
devices.on()
break
case "off":
devices.off()
break
default:
httpError(403, "Access denied. This command is not supported by current capability.")
}
} }
} }
@@ -77,7 +86,16 @@ private void update(devices) {
if (!device) { if (!device) {
httpError(404, "Device not found") httpError(404, "Device not found")
} else { } else {
device."$command"() switch(command) {
case "on":
device.on()
break
case "off":
device.off()
break
default:
httpError(403, "Access denied. This command is not supported by current capability.")
}
} }
} }
} }

View File

@@ -58,7 +58,7 @@ def authPage() {
if (canInstallLabs()) { if (canInstallLabs()) {
def redirectUrl = getBuildRedirectUrl() def redirectUrl = getBuildRedirectUrl()
log.debug "Redirect url = ${redirectUrl}" // log.debug "Redirect url = ${redirectUrl}"
if (state.authToken) { if (state.authToken) {
description = "Tap 'Next' to proceed" description = "Tap 'Next' to proceed"
@@ -113,13 +113,13 @@ def oauthInitUrl() {
scope: "read_station" scope: "read_station"
] ]
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}" // log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
redirect (location: getVendorAuthPath() + toQueryString(oauthParams)) redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
} }
def callback() { def callback() {
log.debug "callback()>> params: $params, params.code ${params.code}" // log.debug "callback()>> params: $params, params.code ${params.code}"
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
@@ -135,7 +135,7 @@ def callback() {
scope: "read_station" scope: "read_station"
] ]
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}" // log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
def tokenUrl = getVendorTokenPath() def tokenUrl = getVendorTokenPath()
def params = [ def params = [
@@ -144,7 +144,7 @@ def callback() {
body: tokenParams body: tokenParams
] ]
log.debug "PARAMS: ${params}" // log.debug "PARAMS: ${params}"
httpPost(params) { resp -> httpPost(params) { resp ->
@@ -156,7 +156,7 @@ def callback() {
state.refreshToken = data.refresh_token state.refreshToken = data.refresh_token
state.authToken = data.access_token state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000) state.tokenExpires = now() + (data.expires_in * 1000)
log.debug "swapped token: $resp.data" // log.debug "swapped token: $resp.data"
} }
} }
@@ -292,7 +292,7 @@ def refreshToken() {
response.data.each {key, value -> response.data.each {key, value ->
def data = slurper.parseText(key); def data = slurper.parseText(key);
log.debug "Data: $data" // log.debug "Data: $data"
state.refreshToken = data.refresh_token state.refreshToken = data.refresh_token
state.accessToken = data.access_token state.accessToken = data.access_token

View File

@@ -26,9 +26,9 @@ preferences {
} }
section("Temperature monitor?") { section("Temperature monitor?") {
input "temp", "capability.temperatureMeasurement", title: "Temp Sensor", required: false input "temp", "capability.temperatureMeasurement", title: "Temperature Sensor", required: false
input "maxTemp", "number", title: "Max Temp?", required: false input "maxTemp", "number", title: "Max Temperature (°${location.temperatureScale})", required: false
input "minTemp", "number", title: "Min Temp?", required: false input "minTemp", "number", title: "Min Temperature (°${location.temperatureScale})", required: false
} }
section("When which people are away?") { section("When which people are away?") {

View File

@@ -164,7 +164,7 @@ def installed() {
def updated() { def updated() {
unschedule() unschedule()
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
unsubscribe unsubscribe()
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
init() init()
} }

View File

@@ -17,7 +17,7 @@ definition(
name: "Monitor on Sense", name: "Monitor on Sense",
namespace: "resteele", namespace: "resteele",
author: "Rachel Steele", author: "Rachel Steele",
description: "Turn on Monitor when vibration is sensed", description: "Turn on switch when vibration is sensed",
category: "My Apps", category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
@@ -25,10 +25,10 @@ definition(
preferences { preferences {
section("When the keyboard is used...") { section("When vibration is sensed...") {
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?" input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
} }
section("Turn on/off a light...") { section("Turn on switch...") {
input "switch1", "capability.switch" input "switch1", "capability.switch"
} }
} }
@@ -47,5 +47,3 @@ def updated() {
def accelerationActiveHandler(evt) { def accelerationActiveHandler(evt) {
switch1.on() switch1.on()
} }

View File

@@ -114,13 +114,16 @@ def beaconHandler(evt) {
if (allOk) { if (allOk) {
def data = new groovy.json.JsonSlurper().parseText(evt.data) def data = new groovy.json.JsonSlurper().parseText(evt.data)
log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId // removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
def beaconName = getBeaconName(evt) def beaconName = getBeaconName(evt)
log.debug "<beacon-control> beaconName: $beaconName" // removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> beaconName: $beaconName"
def phoneName = getPhoneName(data) def phoneName = getPhoneName(data)
log.debug "<beacon-control> phoneName: $phoneName" // removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> phoneName: $phoneName"
if (phoneName != null) { if (phoneName != null) {
def action = data.presence == "1" ? "arrived" : "left" def action = data.presence == "1" ? "arrived" : "left"
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName" def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"

View File

@@ -49,13 +49,15 @@ preferences {
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installed with settings: ${settings}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" // commented out log statement because presence sensor label could contain user's name
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" // commented out log statement because presence sensor label could contain user's name
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
unsubscribe() unsubscribe()
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }

View File

@@ -20,6 +20,8 @@
* JLH - 02-15-2014 - Fuller use of ecobee API * JLH - 02-15-2014 - Fuller use of ecobee API
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines * 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
*/ */
include 'asynchttp_v1'
definition( definition(
name: "Ecobee (Connect)", name: "Ecobee (Connect)",
namespace: "smartthings", namespace: "smartthings",
@@ -116,7 +118,6 @@ def callback() {
def oauthState = params.state def oauthState = params.state
if (oauthState == atomicState.oauthInitState) { if (oauthState == atomicState.oauthInitState) {
def tokenParams = [ def tokenParams = [
grant_type: "authorization_code", grant_type: "authorization_code",
code : code, code : code,
@@ -129,9 +130,6 @@ def callback() {
httpPost(uri: tokenUrl) { resp -> httpPost(uri: tokenUrl) { resp ->
atomicState.refreshToken = resp.data.refresh_token atomicState.refreshToken = resp.data.refresh_token
atomicState.authToken = resp.data.access_token atomicState.authToken = resp.data.access_token
log.debug "swapped token: $resp.data"
log.debug "atomicState.refreshToken: ${atomicState.refreshToken}"
log.debug "atomicState.authToken: ${atomicState.authToken}"
} }
if (atomicState.authToken) { if (atomicState.authToken) {
@@ -200,7 +198,6 @@ def connectionStatus(message, redirectUrl = null) {
.container { .container {
width: 90%; width: 90%;
padding: 4%; padding: 4%;
/*background: #eee;*/
text-align: center; text-align: center;
} }
img { img {
@@ -237,19 +234,24 @@ def getEcobeeThermostats() {
log.debug "getting device list" log.debug "getting device list"
atomicState.remoteSensors = [] atomicState.remoteSensors = []
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}' def bodyParams = [
selection: [
selectionType: "registered",
selectionMatch: "",
includeRuntime: true,
includeSensors: true
]
]
def deviceListParams = [ def deviceListParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [format: 'json', body: requestBody] query: [json: toJson(bodyParams)]
] ]
def stats = [:] def stats = [:]
try { try {
httpGet(deviceListParams) { resp -> httpGet(deviceListParams) { resp ->
if (resp.status == 200) { if (resp.status == 200) {
resp.data.thermostatList.each { stat -> resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
@@ -263,9 +265,8 @@ def getEcobeeThermostats() {
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken([async: false, nextAction: "getEcobeeThermostats"])
} }
} }
atomicState.thermostats = stats atomicState.thermostats = stats
@@ -289,8 +290,9 @@ Map sensorsDiscovered() {
} }
def getThermostatDisplayName(stat) { def getThermostatDisplayName(stat) {
if(stat?.name) if(stat?.name) {
return stat.name.toString() return stat.name.toString()
}
return (getThermostatTypeName(stat) + " (${stat.identifier})").toString() return (getThermostatTypeName(stat) + " (${stat.identifier})").toString()
} }
@@ -310,7 +312,6 @@ def updated() {
} }
def initialize() { def initialize() {
log.debug "initialize" log.debug "initialize"
def devices = thermostats.collect { dni -> def devices = thermostats.collect { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
@@ -350,24 +351,28 @@ def initialize() {
log.warn "delete: ${delete}, deleting ${delete.size()} thermostats" log.warn "delete: ${delete}, deleting ${delete.size()} thermostats"
delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management) delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management)
atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected //send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings" def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = null atomicState.timeSendPush = null
atomicState.reAttempt = 0 atomicState.reAttempt = 0
pollHandler() //first time polling data data from thermostat initialPoll() //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")
} }
def pollHandler() { /**
log.debug "pollHandler()" * Polls the child devices (synchronously).
pollChildren(null) // Hit the ecobee API for update on all thermostats * This is used during app install/update, and is synchronous
* to maintain current behavior that will cause install/update to fail
* if polling fails.
*/
def initialPoll() {
log.debug "initialPoll()"
pollChildrenSync() // Hit the ecobee API for update on all thermostats
atomicState.thermostats.each {stat -> atomicState.thermostats.each {stat ->
def dni = stat.key def dni = stat.key
@@ -380,73 +385,41 @@ def pollHandler() {
} }
} }
def pollChildren(child = null) { /**
def thermostatIdsString = getChildDeviceIdsString() * Polls Ecobee (asynchronously) for updated device state data.
* Called from within this Connect SmartApp as well as the child
* devices.
*/
def poll() {
log.debug "polling asynchronously"
asynchttp_v1.get('asyncPollResponseHandler', getPollParams())
}
/**
* Makes a (synchronous) request to the Ecobee API to get the data for the thermostats.
* This request is made synchronously here because it is called as part of the
* install/updated lifecycle, and changing it to asynchronous during the install/update
* lifecycle may change the behavior if there is an error in polling.
*
* If further analysis shows that polling can be done asynchronously during
* install/update without any adverse consequences, this should then be made
* asynchronous just as the scheduled polling is.
*/
def pollChildrenSync() {
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 params = getPollParams()
params.query << ["Content-Type": "application/json"]
def result = false def result = false
log.debug "making synchronous poll request"
def pollParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [format: 'json', body: jsonRequestBody]
]
try{ try{
httpGet(pollParams) { resp -> httpGet(params) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
atomicState.thermostatData = resp.data
updateSensorData() updateSensorData()
atomicState.thermostats = resp.data.thermostatList.inject([:]) { collector, stat -> storeThermostatData(resp.data.thermostatList)
def dni = [ app.id, stat.identifier ].join('.')
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F")
{
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data]
return collector
}
result = true result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
} }
@@ -454,25 +427,88 @@ def pollChildren(child = null) {
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken([async: false, nextAction: "pollChildrenSync"])
} }
} }
return result return result
} }
// Poll Child is invoked from the Child Device itself as part of the Poll Capability /**
def pollChild(){ * Response handler for asynchronous request to get thermostat data.
* Given a successful response, updates the sensor data, stores the thermostat
* data, and generates child device events.
*
* If the access token has expired, will issue a request to refresh the token
* (and pending successful token refresh, the poll request will be made again).
*/
def asyncPollResponseHandler(response, data) {
log.trace "async poll response handler"
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error ("error parsing JSON", e)
}
if (json) {
atomicState.remoteSensors = json.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(json.thermostatList)
generateChildThermostatEvent()
}
} else {
log.warn "Response returned non-200 response. Status: ${response.status}, data: ${response.getData()}"
}
} else {
log.trace "Exception polling children: ${response.getErrorMessage()}"
def errorJson
try {
errorJson = response.getErrorJson()
} catch (e) {
log.error("Unable to parse error json response", e)
}
if (errorJson?.status?.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: true, nextAction: "poll"])
} else {
log.warn "Error polling children that is not due to an expired token. Response: ${response.getErrorData()}"
}
}
}
def devices = getChildDevices() private getPollParams() {
def thermostatIdsString = getChildDeviceIdsString()
def requestBody = [
selection: [
selectionType: "thermostats",
selectionMatch: thermostatIdsString,
includeExtendedRuntime: true,
includeSettings: true,
includeRuntime: true,
includeSensors: true
]
]
return [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(requestBody)]
]
}
if (pollChildren()){ /**
devices.each { child -> * Calls each child thermostat device to generate an event with the thermostat
* data.
*/
def generateChildThermostatEvent() {
log.trace("generateChildThermostatEvent")
getChildDevices().each { child ->
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) { if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId] def tData = atomicState.thermostats[child.device.deviceNetworkId]
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.debug "calling child.generateEvent($tData.data)"
child.generateEvent(tData.data) //parse received message from parent child.generateEvent(tData.data) //parse received message from parent
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) { } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
@@ -480,48 +516,10 @@ def pollChild(){
} }
} }
} }
} else {
log.info "ERROR: pollChildren()"
return null
}
}
void poll() {
pollChild()
} }
def availableModes(child) { def availableModes(child) {
debugEvent ("atomicState.thermostats = ${atomicState.thermostats}") debugEvent ("atomicState.thermostats = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId]
debugEvent("Data = ${tData}")
if(!tData)
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null
}
def modes = ["off"]
if (tData.data.heatMode) modes.add("heat")
if (tData.data.coolMode) modes.add("cool")
if (tData.data.autoMode) modes.add("auto")
if (tData.data.auxHeatMode) modes.add("auxHeatOnly")
modes
}
def currentMode(child) {
debugEvent ("atomicState.Thermos = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}") debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId] def tData = atomicState.thermostats[child.device.deviceNetworkId]
@@ -530,14 +528,42 @@ def currentMode(child) {
if(!tData) { if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null
}
def modes = ["off"]
if (tData.data.heatMode) {
modes.add("heat")
}
if (tData.data.coolMode) {
modes.add("cool")
}
if (tData.data.autoMode) {
modes.add("auto")
}
if (tData.data.auxHeatMode) {
modes.add("auxHeatOnly")
}
return modes
}
def currentMode(child) {
debugEvent ("atomicState.Thermos = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId]
debugEvent("Data = ${tData}")
if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null return null
} }
def mode = tData.data.thermostatMode def mode = tData.data.thermostatMode
return mode
mode
} }
def updateSensorData() { def updateSensorData() {
@@ -558,12 +584,12 @@ def updateSensorData() {
} }
} }
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {
if(it.value == "true") if(it.value == "true") {
occupancy = "active" occupancy = "active"
else } else {
occupancy = "inactive" occupancy = "inactive"
}
} }
} }
def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code
@@ -582,144 +608,280 @@ def getChildDeviceIdsString() {
} }
def toJson(Map m) { def toJson(Map m) {
return new org.json.JSONObject(m).toString() return groovy.json.JsonOutput.toJson(m)
} }
def toQueryString(Map m) { def toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&") return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
} }
private refreshAuthToken() { /**
log.debug "refreshing auth token" * Uses the refresh token to get a new access token, then executes the nextAction.
* @param options - a map of options. valid options are async: true/false, which
* specifies if the refresh token request will be done asynchronously or not (default is false)
* nextAction: "nameOfMethod" specifies what method to execute after
* the token is refreshed (not required).
* (note: using a map as the parameter because we need to call it from a schedueled
* execution and we can only pass a data map to scheduled executions)
*/
private void refreshAuthToken(options) {
if(!atomicState.refreshToken) { if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored" log.warn "Cannot not refresh OAuth token since there is no refreshToken stored"
} else { } else {
def refreshParams = [ def refreshParams = [
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],
] ]
if (options.async) {
refreshAuthTokenAsync(refreshParams, options.nextAction)
} else {
refreshAuthTokenSync(refreshParams, options.nextAction)
}
}
}
log.debug refreshParams private void refreshAuthTokenSync(params, nextAction = null) {
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
try { try {
def jsonMap
httpPost(refreshParams) { resp -> httpPost(refreshParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!" log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!") debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data, nextAction)
log.debug resp
jsonMap = resp.data
if(resp.data) {
log.debug resp.data
debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"()
atomicState.action = ""
}
}
atomicState.action = ""
} }
} }
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec reauthTokenErrorHandler(e.statusCode)
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc. }
runIn(reAttemptPeriod, "refreshAuthToken") }
} else if (e.statusCode == 401) { // unauthorized
private void refreshAuthTokenAsync(refreshParams, nextAction = null) {
log.debug "making asynchronous refresh request"
asynchttp_v1.post('refreshTokenResponseHandler', refreshParams, [nextAction: nextAction])
}
/**
* The response handler for the request to refresh the authorization handler.
* Stores the new authorization token and refresh token, and executes any action
* (method) that failed due to the authorization token expiring.
*/
private void refreshTokenResponseHandler(response, data) {
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error "error parsing json from response data: $response.data"
}
if (json) {
log.debug "asnyc refreshTokenHandler: Token refreshed...calling saved RestAction now!"
debugEvent("async Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(json, data.nextAction)
} else {
log.warn "successfully parsed json but result is empty or null"
}
} else {
log.debug "Non 200 response returned. Response code: ${response.code}, data: ${response.getData()}"
}
} else {
log.debug "async refreshTokenHandler: RESPONSE ERROR: ${response.getErrorJson()}"
reauthTokenErrorHandler(response.getErrorJson().code)
}
}
/**
* Retries refreshing the authorization token. Will attempt to get the refresh
* token later, in case there were errors retrieving it.
* Will retry a fixed number of times before sending a push notification to the
* user instructing them to reauthenticate
*/
private void reauthTokenErrorHandler(responseCode) {
def retryInterval = 300 // in seconds
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."
// might get non-401 error from exceeding 20 second app limit, connectivity issues, etc.
if (responseCode != 401) {
runIn(retryInterval, "refreshAuthToken", [async: true])
} else if (responseCode == 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) {
runIn(reAttemptPeriod, "refreshAuthToken") runIn(retryInterval, "refreshAuthToken", [async: true])
} else { } else {
sendPushAndFeeds(notificationMessage) sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0 atomicState.reAttempt = 0
} }
} }
} }
/**
* Saves the refresh and auth token from the passed-in JSON object,
* and invokes any previously executing action that did not complete due to
* an expired token.
*
* @param json - an object representing the parsed JSON response from Ecobee
*/
private void saveTokenAndResumeAction(json, String nextAction) {
def debugMessage = "token response, scope: ${json?.scope}, expires_in: ${json?.expires_in}, token_type: ${json?.token_type}"
log.debug "debugMessage"
if (json) {
debugEvent(debugMessage)
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (nextAction) {
log.debug "got refresh token, will execute next action (passed in!): $nextAction"
"$nextAction"()
}
} else {
log.warn "did not get response body from refresh token response"
} }
} }
def resumeProgram(child, deviceId) { /**
* Executes the resume program command on the Ecobee thermostat
* @param deviceId - the ID of the device
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}' *
def result = sendJson(jsonRequestBody) * @retrun true if the command was successful, false otherwise.
return result */
boolean resumeProgram(deviceId) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "resumeProgram"
]
]
]
return sendCommandToEcobee(payload)
} }
def setHold(child, heating, cooling, deviceId, sendHoldType) { /**
* Executes the set hold command on the Ecobee thermostat
int h = heating * 10 * @param heating - The heating temperature to set in fahrenheit
int c = cooling * 10 * @param cooling - the cooling temperature to set in fahrenheit
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}' * @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
def result = sendJson(child, jsonRequestBody) *
return result * @return true if the command was successful, false otherwise
} */
boolean setHold(heating, cooling, deviceId, sendHoldType) {
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) { // Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10 int h = heating * 10
int c = cooling * 10 int c = cooling * 10
def payload = [
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+', "fan": '+fanMode+' } } ]}' return sendCommandToEcobee(payload)
def result = sendJson(child, jsonRequestBody)
return result
} }
def setMode(child, mode, deviceId) { /**
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}' * Executes the set fan mode command on the Ecobee thermostat
* @param heating - The heating temperature to set in fahrenheit
* @param cooling - the cooling temperature to set in fahrenheit
* @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
* @param fanMode - the fan mode to set to
*
* @return true if the command was successful, false otherwise
*/
boolean setFanMode(heating, cooling, deviceId, sendHoldType, fanMode) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
def result = sendJson(jsonRequestBody) def payload = [
return result selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType,
fan: fanMode
]
]
]
]
return sendCommandToEcobee(payload)
} }
def sendJson(child = null, String jsonBody) { /**
* Sets the mode of the Ecobee thermostat
* @param mode - the mode to set to
* @param deviceId - the ID of the device
*
* @return true if the command was successful, false otherwise
*/
boolean setMode(mode, deviceId) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
thermostat: [
settings: [
hvacMode: mode
]
]
]
return sendCommandToEcobee(payload)
}
def returnStatus = false /**
* Makes a request to the Ecobee API to actuate the thermostat.
* Used by command methods to send commands to Ecobee.
*
* @param bodyParams - a map of request parameters to send to Ecobee.
*
* @return true if the command was accepted by Ecobee without error, false otherwise.
*/
private boolean sendCommandToEcobee(Map bodyParams) {
def isSuccess = false
def cmdParams = [ def cmdParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
body: jsonBody body: toJson(bodyParams)
] ]
try{ try{
httpPost(cmdParams) { resp -> httpPost(cmdParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
def returnStatus = resp.data.status.code
log.debug "updated ${resp.data}" if (returnStatus == 0) {
returnStatus = resp.data.status.code
if (resp.data.status.code == 0)
log.debug "Successful call to ecobee API." log.debug "Successful call to ecobee API."
else { isSuccess = true
log.debug "Error return code = ${resp.data.status.code}" } else {
debugEvent("Error return code = ${resp.data.status.code}") log.debug "Error return code = ${returnStatus}"
debugEvent("Error return code = ${returnStatus}")
} }
} }
} }
@@ -727,33 +889,29 @@ def sendJson(child = null, String jsonBody) {
log.trace "Exception Sending Json: " + e.response.data.status log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren" // TODO - figure out why we're setting the next action to be poll
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken([async: true, nextAction: "poll"])
} } else {
else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.") debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc." log.error "Authentication error, invalid authentication method, lack of credentials, etc."
} }
} }
if (returnStatus == 0) return isSuccess
return true
else
return false
} }
def getChildName() { "Ecobee Thermostat" } def getChildName() { return "Ecobee Thermostat" }
def getSensorChildName() { "Ecobee Sensor" } def getSensorChildName() { return "Ecobee Sensor" }
def getServerUrl() { return "https://graph.api.smartthings.com" } def getServerUrl() { return "https://graph.api.smartthings.com" }
def getShardUrl() { return getApiServerUrl() } def getShardUrl() { return getApiServerUrl() }
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" } def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" }
def getApiEndpoint() { "https://api.ecobee.com" } def getApiEndpoint() { return "https://api.ecobee.com" }
def getSmartThingsClientId() { appSettings.clientId } def getSmartThingsClientId() { return appSettings.clientId }
def debugEvent(message, displayEvent = false) { def debugEvent(message, displayEvent = false) {
def results = [ def results = [
name: "appdebug", name: "appdebug",
descriptionText: message, descriptionText: message,
@@ -761,11 +919,6 @@ def debugEvent(message, displayEvent = false) {
] ]
log.debug "Generating AppDebug Event: ${results}" log.debug "Generating AppDebug Event: ${results}"
sendEvent (results) sendEvent (results)
}
def debugEventFromParent(child, message) {
if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)}
} }
//send both push notification and mobile activity feeds //send both push notification and mobile activity feeds
@@ -786,6 +939,58 @@ def sendPushAndFeeds(notificationMessage){
atomicState.authToken = null atomicState.authToken = null
} }
/**
* Stores data about the thermostats in atomicState.
* @param thermostats - a list of thermostats as returned from the Ecobee API
*/
private void storeThermostatData(thermostats) {
log.trace "Storing thermostat data: $thermostats"
def data
atomicState.thermostats = thermostats.inject([:]) { collector, stat ->
def dni = [ app.id, stat.identifier ].join('.')
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F") {
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data]
return collector
}
log.debug "updated ${atomicState.thermostats?.size()} thermostats: ${atomicState.thermostats}"
}
def sendActivityFeeds(notificationMessage) { def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices() def devices = getChildDevices()
devices.each { child -> devices.each { child ->
@@ -793,14 +998,6 @@ def sendActivityFeeds(notificationMessage) {
} }
} }
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) { def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2) return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
} }
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -64,10 +64,12 @@ def meterHandler(evt) {
def lastValue = atomicState.lastValue as double def lastValue = atomicState.lastValue as double
atomicState.lastValue = meterValue atomicState.lastValue = meterValue
def dUnit = evt.unit ?: "Watts"
def aboveThresholdValue = aboveThreshold as int def aboveThresholdValue = aboveThreshold as int
if (meterValue > aboveThresholdValue) { if (meterValue > aboveThresholdValue) {
if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${evt.unit} which is above your threshold of ${aboveThreshold}." def msg = "${meter} reported ${evt.value} ${dUnit} which is above your threshold of ${aboveThreshold}."
sendMessage(msg) sendMessage(msg)
} else { } else {
// log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed" // log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed"
@@ -78,7 +80,7 @@ def meterHandler(evt) {
def belowThresholdValue = belowThreshold as int def belowThresholdValue = belowThreshold as int
if (meterValue < belowThresholdValue) { if (meterValue < belowThresholdValue) {
if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${evt.unit} which is below your threshold of ${belowThreshold}." def msg = "${meter} reported ${evt.value} ${dUnit} which is below your threshold of ${belowThreshold}."
sendMessage(msg) sendMessage(msg)
} else { } else {
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed" // log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"

View File

@@ -54,10 +54,10 @@ def waterWetHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone within the last $deltaSeconds seconds" log.debug "SMS already sent within the last $deltaSeconds seconds"
} else { } else {
def msg = "${alarm.displayName} is wet!" def msg = "${alarm.displayName} is wet!"
log.debug "$alarm is wet, texting $phone" log.debug "$alarm is wet, texting phone number"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients) sendNotificationToContacts(msg, recipients)

View File

@@ -90,7 +90,7 @@ def takeAction(){
} }
def sendTextMessage() { def sendTextMessage() {
log.debug "$multisensor was open too long, texting $phone" log.debug "$multisensor was open too long, texting phone"
updateSmsHistory() updateSmsHistory()
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1) def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)

View File

@@ -761,7 +761,7 @@ String displayableTime(timeRemaining) {
return "${minutes}:00" return "${minutes}:00"
} }
def fraction = "0.${parts[1]}" as double def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padRight(2, "0") def seconds = "${60 * fraction as int}".padLeft(2, "0")
return "${minutes}:${seconds}" return "${minutes}:${seconds}"
} }

View File

@@ -47,13 +47,13 @@ preferences {
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installed with settings: ${settings}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" // log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" // log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
unsubscribe() unsubscribe()
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
@@ -71,11 +71,10 @@ def presence(evt)
def person = getPerson(evt) def person = getPerson(evt)
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"} def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
if (recentNotPresent) { if (recentNotPresent) {
log.debug "skipping notification of arrival of ${person.displayName} because last departure was only ${now() - recentNotPresent.date.time} msec ago" log.debug "skipping notification of arrival of Person because last departure was only ${now() - recentNotPresent.date.time} msec ago"
} }
else { else {
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'" def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
log.info message
send(message) send(message)
setLocationMode(newMode) setLocationMode(newMode)
} }
@@ -106,6 +105,4 @@ private send(msg) {
sendSms(phone, msg) sendSms(phone, msg)
} }
} }
log.debug msg
} }

View File

@@ -57,12 +57,11 @@ def scheduleCheck()
def message = message1 ?: "SmartThings - Habit Helper Reminder!" def message = message1 ?: "SmartThings - Habit Helper Reminder!"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "Texting reminder: ($message) to contacts:${recipients?.size()}" log.debug "Texting reminder to contacts:${recipients?.size()}"
sendNotificationToContacts(message, recipients) sendNotificationToContacts(message, recipients)
} }
else { else {
log.debug "Texting reminder"
log.debug "Texting reminder: ($message) to $phone1"
sendSms(phone1, message) sendSms(phone1, message)
} }
} }

View File

@@ -95,8 +95,7 @@ def bridgeDiscoveryFailed() {
} }
} }
def bridgeLinking() def bridgeLinking() {
{
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
state.linkRefreshcount = linkRefreshcount + 1 state.linkRefreshcount = linkRefreshcount + 1
def refreshInterval = 3 def refreshInterval = 3
@@ -171,7 +170,7 @@ def bulbDiscovery() {
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, options:newLights input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
} }
section { section {
@@ -328,7 +327,7 @@ def bulbListHandler(hub, data = "") {
def object = new groovy.json.JsonSlurper().parseText(data) def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v -> object.each { k,v ->
if (v instanceof Map) if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub] bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
} }
} }
def bridge = null def bridge = null
@@ -448,7 +447,6 @@ def addBridge() {
updateBridgeStatus(childDevice) updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber) childDevice.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) { if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) { if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -649,8 +647,7 @@ def locationHandler(evt) {
} }
} }
} }
} } else if (parsedEvent.headers && parsedEvent.body) {
else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES" log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString() def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) { if (headerString?.contains("xml")) {
@@ -727,13 +724,13 @@ private void updateBridgeStatus(childDevice) {
} }
/** /**
* Check if all Hue bridges have been heard from in the last 16 minutes, if not an Offline event will be sent * Check if all Hue bridges have been heard from in the last 11 minutes, if not an Offline event will be sent
* for the bridge. Also, set ID number on bridge if not done previously. * for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
*/ */
private void checkBridgeStatus() { private void checkBridgeStatus() {
def bridges = getHueBridges() def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer) // Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 16) def time = now() - (1000 * 60 * 11)
bridges.each { bridges.each {
def d = getChildDevice(it.value.mac) def d = getChildDevice(it.value.mac)
if(d) { if(d) {
@@ -746,6 +743,13 @@ private void checkBridgeStatus() {
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each {
it.value.online = false
}
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else { } else {
d.sendEvent(name: "status", value: "Online")//setOnline(false) d.sendEvent(name: "status", value: "Online")//setOnline(false)
} }
@@ -785,8 +789,7 @@ def parse(childDevice, description) {
if (body instanceof java.util.Map) { if (body instanceof java.util.Map) {
// get (poll) reponse // get (poll) reponse
return handlePoll(body) return handlePoll(body)
} } else {
else {
//put response //put response
return handleCommandResponse(body) return handleCommandResponse(body)
} }
@@ -907,10 +910,14 @@ private handleCommandResponse(body) {
// send events for each update found above (order of events should be same as handlePoll()) // send events for each update found above (order of events should be same as handlePoll())
updates.each { childDeviceNetworkId, params -> updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId) def device = getChildDevice(childDeviceNetworkId)
def id = getId(device)
// If device is offline, then don't send events which will update device watch
if (isOnline(id)) {
sendBasicEvents(device, "on", params.on) sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri) sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
} }
}
return [] return []
} }
@@ -932,21 +939,28 @@ private handleCommandResponse(body) {
* @return empty array * @return empty array
*/ */
private handlePoll(body) { private handlePoll(body) {
if (state.updating) {
// If user just executed commands, then ignore poll to not confuse the turning on/off state
return []
}
def bulbs = getChildDevices() def bulbs = getChildDevices()
for (bulb in body) { for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (device) { if (device) {
if (bulb.value.state?.reachable) { if (bulb.value.state?.reachable) {
if (state.bulbs[bulb.key]?.online == false) {
// light just came back online, notify device watch
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
}
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on) sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri) sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
} else { } else {
state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge" log.warn "$device is not reachable by Hue bridge"
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
} }
} }
} }
@@ -980,22 +994,34 @@ def hubVerification(bodytext) {
def on(childDevice) { def on(childDevice) {
log.debug "Executing 'on'" log.debug "Executing 'on'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [on: true]) put("lights/$id/state", [on: true])
return "Bulb is turning On" return "Bulb is turning On"
} }
def off(childDevice) { def off(childDevice) {
log.debug "Executing 'off'" log.debug "Executing 'off'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "off") createSwitchEvent(childDevice, "off")
put("lights/${getId(childDevice)}/state", [on: false]) put("lights/$id/state", [on: false])
return "Bulb is turning Off" return "Bulb is turning Off"
} }
def setLevel(childDevice, percent) { def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 1 - 254 // 1 - 254
def level def level
@@ -1010,48 +1036,64 @@ def setLevel(childDevice, percent) {
// that means that the light will still be on when on is called next time // that means that the light will still be on when on is called next time
// Lets emulate that here // Lets emulate that here
if (percent > 0) { if (percent > 0) {
put("lights/${getId(childDevice)}/state", [bri: level, on: true]) put("lights/$id/state", [bri: level, on: true])
} else { } else {
put("lights/${getId(childDevice)}/state", [on: false]) put("lights/$id/state", [on: false])
} }
return "Setting level to $percent" return "Setting level to $percent"
} }
def setSaturation(childDevice, percent) { def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'" log.debug "Executing 'setSaturation($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 0 - 254 // 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254) def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [sat: level, on: true]) put("lights/$id/state", [sat: level, on: true])
return "Setting saturation to $percent" return "Setting saturation to $percent"
} }
def setHue(childDevice, percent) { def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'" log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 0 - 65535 // 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535) def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [hue: level, on: true]) put("lights/$id/state", [hue: level, on: true])
return "Setting hue to $percent" return "Setting hue to $percent"
} }
def setColorTemperature(childDevice, huesettings) { def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'" log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 153 (6500K) to 500 (2000K) // 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [ct: ct, on: true]) put("lights/$id/state", [ct: ct, on: true])
return "Setting color temperature to $percent" return "Setting color temperature to $percent"
} }
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
def value = [:] def value = [:]
@@ -1108,15 +1150,23 @@ def setColor(childDevice, huesettings) {
value.on = false value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off") createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/${getId(childDevice)}/state", value) put("lights/$id/state", value)
return "Setting color to $value" return "Setting color to $value"
} }
def ping(childDevice) {
if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) { private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) { if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1] return childDevice.device?.deviceNetworkId[3..-1]
} } else {
else {
return childDevice.device?.deviceNetworkId.split("/")[-1] return childDevice.device?.deviceNetworkId.split("/")[-1]
} }
} }
@@ -1127,10 +1177,13 @@ private poll() {
log.debug "GET: $host$uri" log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host} HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue)) """, physicalgraph.device.Protocol.LAN, selectedHue))
} }
private isOnline(id) {
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
}
private put(path, body) { private put(path, body) {
def host = getBridgeIP() def host = getBridgeIP()
def uri = "/api/${state.username}/$path" def uri = "/api/${state.username}/$path"
@@ -1198,7 +1251,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) { if (state.bulbs instanceof java.util.List) {
def map = [:] def map = [:]
state.bulbs.unique {it.id}.each { bulb -> state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]] map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
} }
state.bulbs = map state.bulbs = map
} }

View File

@@ -53,14 +53,14 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds" log.debug "SMS already sent within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "$accelerationSensor has moved, texting contacts: ${recipients?.size()}" log.debug "accelerationSensor has moved, texting contacts: ${recipients?.size()}"
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients) sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
} }
else { else {
log.debug "$accelerationSensor has moved, texting $phone1" log.debug "accelerationSensor has moved, sending text message"
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved") sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
} }
} }

View File

@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1 def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes" log.debug "SMS already sent within the last $deltaMinutes minutes"
// TODO: Send "Temperature back to normal" SMS, turn switch off // TODO: Send "Temperature back to normal" SMS, turn switch off
} else { } else {
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch" log.debug "Temperature rose above $tooHot: sending SMS and activating $mySwitch"
def tempScale = location.temperatureScale ?: "F" def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}") send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on() switch1?.on()

View File

@@ -51,8 +51,8 @@ def authPage() {
def description = "Tap to enter LIFX credentials" def description = "Tap to enter LIFX credentials"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
// def redirectUrl = "${apiServerUrl}" // def redirectUrl = "${apiServerUrl}"
log.debug "app id: ${app.id}" // log.debug "app id: ${app.id}"
log.debug "redirect url: ${redirectUrl}" // log.debug "redirect url: ${redirectUrl}"s
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) { return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
section { section {
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account") href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
@@ -372,7 +372,7 @@ def updateDevices() {
def childDevice = getChildDevice(device.id) def childDevice = getChildDevice(device.id)
selectors.add("${device.id}") selectors.add("${device.id}")
if (!childDevice) { if (!childDevice) {
log.info("Adding device ${device.id}: ${device.product}") // log.info("Adding device ${device.id}: ${device.product}")
def data = [ def data = [
label: device.label, label: device.label,
level: Math.round((device.brightness ?: 1) * 100), level: Math.round((device.brightness ?: 1) * 100),

View File

@@ -41,10 +41,10 @@ def updated() {
def presenceHandler(evt) { def presenceHandler(evt) {
if (evt.value == "present") { if (evt.value == "present") {
log.debug "${presence.label ?: presence.name} has arrived at the ${location}" // log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
sendPush("${presence.label ?: presence.name} has arrived at the ${location}") sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
} else if (evt.value == "not present") { } else if (evt.value == "not present") {
log.debug "${presence.label ?: presence.name} has left the ${location}" // log.debug "${presence.label ?: presence.name} has left the ${location}"
sendPush("${presence.label ?: presence.name} has left the ${location}") sendPush("${presence.label ?: presence.name} has left the ${location}")
} }
} }

View File

@@ -47,7 +47,7 @@ def updated() {
def presenceHandler(evt) { def presenceHandler(evt) {
if (evt.value == "present") { if (evt.value == "present") {
log.debug "${presence.label ?: presence.name} has arrived at the ${location}" // log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients) sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
@@ -56,7 +56,7 @@ def presenceHandler(evt) {
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}") sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
} }
} else if (evt.value == "not present") { } else if (evt.value == "not present") {
log.debug "${presence.label ?: presence.name} has left the ${location}" // log.debug "${presence.label ?: presence.name} has left the ${location}"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients) sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)

View File

@@ -67,7 +67,7 @@ def updated() {
} }
def subscribe() { def subscribe() {
log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}" // log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
subscribe(doorSensor, "contact", garageDoorContact) subscribe(doorSensor, "contact", garageDoorContact)
subscribe(cars, "presence", carPresence) subscribe(cars, "presence", carPresence)

View File

@@ -71,7 +71,7 @@ def updated() {
private subscribeToEvents() private subscribeToEvents()
{ {
subscribe intrusionMotions, "motion", intruderMotion subscribe intrusionMotions, "motion", intruderMotion
subscribe residentMotions, "motion", residentMotion // subscribe residentMotions, "motion", residentMotion
subscribe intrusionContacts, "contact", contact subscribe intrusionContacts, "contact", contact
subscribe alarms, "alarm", alarm subscribe alarms, "alarm", alarm
subscribe(app, appTouch) subscribe(app, appTouch)
@@ -156,6 +156,7 @@ def residentMotion(evt)
// startReArmSequence() // startReArmSequence()
// } // }
//} //}
unsubscribe(residentMotions)
} }
def contact(evt) def contact(evt)

View File

@@ -48,7 +48,7 @@ def updated()
def contactOpenHandler(evt) { def contactOpenHandler(evt) {
log.trace "$evt.value: $evt, $settings" log.trace "$evt.value: $evt, $settings"
log.debug "$contact1 was opened, texting $phone1" log.debug "$contact1 was opened, sending text"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients) sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
} }

View File

@@ -60,14 +60,14 @@ def motionActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds" log.debug "SMS already sent within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}" log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients) sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
} }
else { else {
log.debug "$motion1 has moved while you were out, texting $phone1" log.debug "$motion1 has moved while you were out, sending text"
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out") sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
} }
} }

View File

@@ -53,13 +53,13 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds" log.debug "SMS already sent to phone within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("Gun case has moved!", recipients) sendNotificationToContacts("Gun case has moved!", recipients)
} }
else { else {
log.debug "$accelerationSensor has moved, texting $phone1" log.debug "$accelerationSensor has moved, texting phone"
sendSms(phone1, "Gun case has moved!") sendSms(phone1, "Gun case has moved!")
} }
} }

View File

@@ -60,7 +60,7 @@ def authPage() {
def oauthInitUrl() { def oauthInitUrl() {
def token = getToken() def token = getToken()
log.debug "initiateOauth got token: $token" //log.debug "initiateOauth got token: $token"
// store these for validate after the user takes the oauth journey // store these for validate after the user takes the oauth journey
state.oauth_request_token = token.oauth_token state.oauth_request_token = token.oauth_token
@@ -76,7 +76,7 @@ def getToken() {
] ]
def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token" def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token"
def url = buildSignedUrl(requestTokenBaseUrl, params) def url = buildSignedUrl(requestTokenBaseUrl, params)
log.debug "getToken - url: $url" //log.debug "getToken - url: $url"
return getJsonFromUrl(url) return getJsonFromUrl(url)
} }
@@ -182,7 +182,7 @@ def exchangeToken() {
def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token" def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token"
def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret) def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret)
log.debug "signed url: $url with secret $tokenSecret" //log.debug "signed url: $url with secret $tokenSecret"
def token = getJsonFromUrl(url) def token = getJsonFromUrl(url)
@@ -198,8 +198,8 @@ def exchangeToken() {
def load() { def load() {
def json = get(getMeasurement(new Date() - 30)) def json = get(getMeasurement(new Date() - 30))
// removed logging of actual json payload. Can be put back for debugging
log.debug "swapped, then received: $json" log.debug "swapped, then received json"
parse(data:json) parse(data:json)
def html = """ def html = """