Compare commits

...

85 Commits

Author SHA1 Message Date
Vinay Rao
e373b6f92e Merge pull request #1272 from SmartThingsCommunity/staging
Rolling up staging to production for deployment
2016-09-20 11:53:36 -07:00
Vinay Rao
43a1ae6371 Merge pull request #1271 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-09-20 11:52:40 -07:00
Vinay Rao
a441b94a33 Merge pull request #1264 from posborne/bugfix/fix-hue-connect-http-headers
PROB-1373: hue: fix HTTP request headers
2016-09-20 08:43:01 -07:00
Vinay Rao
2a58d7ff62 Merge pull request #1266 from jimmyjames/revert-async-ecobee
Revert "[DVCSMP-1979] Use async http for polling and refresh tokens."
2016-09-19 15:22:20 -07:00
Juan Pablo Risso
260917d515 MKTP-829 - Moving disclaimer to first page (#1261) 2016-09-19 14:01:33 -04:00
Paul Osborne
c1478d3e96 hue: fix HTTP request headers
Previously, the whitespace characters from the file were used for
newlines in HTTP headers.  In order for the HTTP headers sent to
the hue to be valid, line separators must always be \r\n.  Oddly, the
hue seemed to accept and respond to requests with the invalid header
that was being sent but it would cause increased latency for all
other API clients.

In addition to the missing carriage returns, the GET request was also
missing the required blank line which marks the end of the request
headers.

https://smartthings.atlassian.net/browse/PROB-1366
http://status.smartthings.com/incidents/13j8g8g2w7ly
https://community.smartthings.com/t/new-hue-delay/57569
2016-09-19 11:01:52 -05:00
Jim Anderson
8b9bff15dc Revert "[DVCSMP-1979] Use async http for polling and refresh tokens."
This reverts commit 826993cc45.
2016-09-19 09:38:33 -05:00
Lars Finander
75c1ede16c Merge pull request #1260 from larsfinander/SSVD-2737_philips_hue_color_handling_staging
SSVD-2736 Philips Hue: Color Coordinator does not work
2016-09-16 11:33:14 -06:00
Lars Finander
a7acc384a2 SSVD-2736 Philips Hue: Color Coordinator does not work
-SSVD-2631 Double color events
-SSVD-2601 Color picker control does not show the current color
-Changed color model for Philips Hue to use hue/sat instead of x/y
-Added color events in hex
-Added HSV color conversion algorithms
2016-09-16 11:16:31 -06:00
Vinay Rao
c6998e5f1d Merge pull request #1249 from jackchi/healthcheck-12min-checkin
[CHF-363] Set HealthCheck interval to 12 min
2016-09-14 14:37:02 -07:00
jackchi
f95e906d6e [CHF-363] Set HealthCheck interval to 12 min 2016-09-14 13:46:54 -07:00
Jack Chi
4891e3b947 Merge pull request #1245 from jackchi/healthcheck-5min-reporting
[CHF-353] Cree Bulb polling fix; reads status every 5 minutes
2016-09-13 17:03:19 -07:00
jackchi
ae91f9bff5 [CHF-353] Cree Bulb polling fix; reads status every 5 minutes 2016-09-13 17:01:19 -07:00
Vinay Rao
bb87ad2cf0 Merge pull request #1196 from juano2310/disclaimer
MKTP-829 - Adding disclaimer
2016-09-13 15:13:43 -07:00
Vinay Rao
5dff03fb69 Merge pull request #1244 from SmartThingsCommunity/master
Rolling up master to staging for next week's deploy
2016-09-13 13:46:05 -07: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
Vinay Rao
dd7c6b90d5 Merge pull request #1241 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-09-13 12:28:13 -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
juano2310
fe2fbc3b97 MKTP-829 - Adding disclaimer 2016-09-06 14:01:20 -04: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 990 additions and 798 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

@@ -19,7 +19,6 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Polling"
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
@@ -67,12 +66,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,27 +89,24 @@ 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() {
return zigbee.levelRefresh()
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
def poll() { def healthPoll() {
zigbee.onOffRefresh() + zigbee.levelRefresh() def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
} }
def configure() { def configure() {
unschedule()
schedule("0 0/5 * * * ? *", "healthPoll")
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 2 check-in misses from device
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() sendEvent(name: "checkInterval", value: 60 * 12, 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

@@ -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 * 12, 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 * 12, 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 * 12, 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 * 12, 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() {
return zigbee.onOffRefresh()
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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 2 check-in misses from device
zigbee.onOffConfig() + powerConfig() + refresh() sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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
} 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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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
} 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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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
} 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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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
} 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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, 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"))) ){ return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, 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

@@ -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() {
return zigbee.onOffRefresh()
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.onOffRefresh()
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 60 * 12, 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() {
return zigbee.onOffRefresh()
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
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 2 check-in misses from device
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 60 * 12, 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

@@ -28,7 +28,7 @@ mappings {
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] } path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] } path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] } path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] }
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]} path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
path("/oauth/callback") { action: [ GET: "callback" ] } path("/oauth/callback") { action: [ GET: "callback" ] }
} }
@@ -84,6 +84,7 @@ def authPage() {
log.debug "RedirectURL = ${redirectUrl}" log.debug "RedirectURL = ${redirectUrl}"
def donebutton= state.JawboneAccessToken != null def donebutton= state.JawboneAccessToken != null
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) { return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) {
section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description } section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description }
} }
} else { } else {
@@ -234,7 +235,7 @@ def validateCurrentToken() {
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response -> httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
if (response.status == 200) { if (response.status == 200) {
log.debug "${response.data}" log.debug "${response.data}"
log.debug "Setting refresh token to ${response.data.data.refresh_token}" log.debug "Setting refresh token"
state.refreshToken = response.data.data.refresh_token state.refreshToken = response.data.data.refresh_token
} }
} }
@@ -258,7 +259,7 @@ def validateCurrentToken() {
state.remove("refreshToken") state.remove("refreshToken")
} }
} else { } else {
log.debug "Setting access token to ${data.access_token}, refresh token to ${data.refresh_token}" log.debug "Setting access token"
state.JawboneAccessToken = data.access_token state.JawboneAccessToken = data.access_token
state.refreshToken = data.refresh_token state.refreshToken = data.refresh_token
} }
@@ -466,7 +467,7 @@ def hookEventHandler() {
moves = response.data.data.items[0] moves = response.data.data.items[0]
} }
log.debug "Goal = ${goals.move_steps} Steps" log.debug "Goal = ${goals.move_steps} Steps"
log.debug "Steps = ${moves.details.steps} Steps" log.debug "Steps = ${moves.details.steps} Steps"
childDevice?.sendEvent(name:"steps", value: moves.details.steps) childDevice?.sendEvent(name:"steps", value: moves.details.steps)
childDevice?.sendEvent(name:"goal", value: goals.move_steps) childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice) //setColor(moves.details.steps,goals.move_steps,childDevice)

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

@@ -66,7 +66,7 @@ def authPage() {
// get rid of next button until the user is actually auth'd // get rid of next button until the user is actually auth'd
if (!oauthTokenProvided) { if (!oauthTokenProvided) {
return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) { return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) {
section(){ section() {
paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button." paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button."
href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description
} }
@@ -76,7 +76,7 @@ def authPage() {
log.debug "thermostat list: $stats" log.debug "thermostat list: $stats"
log.debug "sensor list: ${sensorsDiscovered()}" log.debug "sensor list: ${sensorsDiscovered()}"
return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) { return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) {
section(""){ section("") {
paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings." paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats]) input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
} }
@@ -84,7 +84,7 @@ def authPage() {
def options = sensorsDiscovered() ?: [] def options = sensorsDiscovered() ?: []
def numFound = options.size() ?: 0 def numFound = options.size() ?: 0
if (numFound > 0) { if (numFound > 0) {
section(""){ section("") {
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings." paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options) input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
} }
@@ -115,13 +115,12 @@ def callback() {
def code = params.code def code = params.code
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,
client_id : smartThingsClientId, client_id : smartThingsClientId,
redirect_uri: callbackUrl redirect_uri: callbackUrl
] ]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}" def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -129,9 +128,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) {
@@ -148,8 +144,8 @@ def callback() {
def success() { def success() {
def message = """ def message = """
<p>Your ecobee Account is now connected to SmartThings!</p> <p>Your ecobee Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p> <p>Click 'Done' to finish setup.</p>
""" """
connectionStatus(message) connectionStatus(message)
} }
@@ -171,64 +167,63 @@ def connectionStatus(message, redirectUrl = null) {
} }
def html = """ def html = """
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta name="viewport" content="width=640"> <meta name="viewport" content="width=640">
<title>Ecobee & SmartThings connection</title> <title>Ecobee & SmartThings connection</title>
<style type="text/css"> <style type="text/css">
@font-face { @font-face {
font-family: 'Swiss 721 W01 Thin'; font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot'); src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'), src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'), url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'), url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg'); url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Swiss 721 W01 Light'; font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot'); src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'), src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'), url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'), url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg'); url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
.container { .container {
width: 90%; width: 90%;
padding: 4%; padding: 4%;
/*background: #eee;*/ text-align: center;
text-align: center; }
} img {
img { vertical-align: middle;
vertical-align: middle; }
} p {
p { font-size: 2.2em;
font-size: 2.2em; font-family: 'Swiss 721 W01 Thin';
font-family: 'Swiss 721 W01 Thin'; text-align: center;
text-align: center; color: #666666;
color: #666666; padding: 0 40px;
padding: 0 40px; margin-bottom: 0;
margin-bottom: 0; }
} span {
span { font-family: 'Swiss 721 W01 Light';
font-family: 'Swiss 721 W01 Light'; }
} </style>
</style> </head>
</head> <body>
<body> <div class="container">
<div class="container">
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/ecobee%402x.png" alt="ecobee icon" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/ecobee%402x.png" alt="ecobee icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" /> <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message} ${message}
</div> </div>
</body> </body>
</html> </html>
""" """
render contentType: 'text/html', data: html render contentType: 'text/html', data: html
} }
@@ -237,19 +232,26 @@ 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] // TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: 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
@@ -289,9 +291,10 @@ 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()
} }
def getThermostatTypeName(stat) { def getThermostatTypeName(stat) {
@@ -310,7 +313,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,8 +352,6 @@ 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)
@@ -381,75 +381,41 @@ def pollHandler() {
} }
def pollChildren(child = null) { def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString() def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString" log.debug "polling children: $thermostatIdsString"
def data = ""
def requestBody = [
selection: [
selectionType: "thermostats",
selectionMatch: thermostatIdsString,
includeExtendedRuntime: true,
includeSettings: true,
includeRuntime: true,
includeSensors: true
]
]
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
def result = false def result = false
def pollParams = [ def pollParams = [
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: jsonRequestBody] // TODO - the query string below is not consistent with the Ecobee docs:
] // https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(requestBody)]
]
try{ try{
httpGet(pollParams) { resp -> httpGet(pollParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}" log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
atomicState.thermostatData = resp.data updateSensorData()
updateSensorData() storeThermostatData(resp.data.thermostatList)
atomicState.thermostats = resp.data.thermostatList.inject([:]) { collector, stat -> result = true
def dni = [ app.id, stat.identifier ].join('.') log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
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
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
} }
} 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
@@ -463,13 +429,12 @@ def pollChildren(child = null) {
} }
// Poll Child is invoked from the Child Device itself as part of the Poll Capability // Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild(){ def pollChild() {
def devices = getChildDevices() def devices = getChildDevices()
if (pollChildren()){ if (pollChildren()) {
devices.each { child -> devices.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.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
@@ -492,36 +457,7 @@ void poll() {
} }
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 +466,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 +522,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,7 +546,7 @@ 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) {
@@ -595,54 +559,24 @@ private refreshAuthToken() {
if(!atomicState.refreshToken) { if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored" log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else { } else {
def refreshParams = [ def refreshParams = [
method: 'POST', method: 'POST',
uri : apiEndpoint, uri : apiEndpoint,
path : "/token", path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId], query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
] ]
log.debug refreshParams
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials." def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost //changed to httpPost
try { try {
def jsonMap def jsonMap
httpPost(refreshParams) { resp -> httpPost(refreshParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!" log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!") debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data)
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 def reAttemptPeriod = 300 // in sec
@@ -662,118 +596,220 @@ private refreshAuthToken() {
} }
} }
def resumeProgram(child, deviceId) { /**
* Saves the refresh and auth token from the passed-in JSON object,
* and invokes any previously executing action that did not complete due to
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}' * an expired token.
def result = sendJson(jsonRequestBody) *
return result * @param json - an object representing the parsed JSON response from Ecobee
*/
private void saveTokenAndResumeAction(json) {
log.debug "token response json: $json"
if (json) {
debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (atomicState.action) {
log.debug "got refresh token, executing next action: ${atomicState.action}"
"${atomicState.action}"()
}
} else {
log.warn "did not get response body from refresh token response"
}
atomicState.action = ""
} }
def setHold(child, heating, cooling, deviceId, sendHoldType) { /**
* Executes the resume program command on the Ecobee thermostat
int h = heating * 10 * @param deviceId - the ID of the device
int c = cooling * 10 *
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}' * @retrun true if the command was successful, false otherwise.
*/
def result = sendJson(child, jsonRequestBody) boolean resumeProgram(deviceId) {
return result def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "resumeProgram"
]
]
]
return sendCommandToEcobee(payload)
} }
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) { /**
* Executes the set hold 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
*
* @return true if the command was successful, false otherwise
*/
boolean setHold(heating, cooling, deviceId, sendHoldType) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
int h = heating * 10 def payload = [
int c = cooling * 10 selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType
]
]
]
]
return sendCommandToEcobee(payload)
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
} }
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) { log.debug "updated ${resp.data}"
def returnStatus = resp.data.status.code
log.debug "updated ${resp.data}" if (returnStatus == 0) {
returnStatus = resp.data.status.code log.debug "Successful call to ecobee API."
if (resp.data.status.code == 0) isSuccess = true
log.debug "Successful call to ecobee API." } else {
else { log.debug "Error return code = ${returnStatus}"
log.debug "Error return code = ${resp.data.status.code}" debugEvent("Error return code = ${returnStatus}")
debugEvent("Error return code = ${resp.data.status.code}") }
} }
} }
}
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
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) {
// TODO - figure out why we're setting the next action to be pollChildren
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
atomicState.action = "pollChildren" atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken() refreshAuthToken()
} } 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,
displayed: displayEvent displayed: displayEvent
] ]
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
def sendPushAndFeeds(notificationMessage){ def sendPushAndFeeds(notificationMessage) {
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}" log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}" log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){ if (atomicState.timeSendPush) {
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day if (now() - atomicState.timeSendPush > 86400000) { // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage) sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage) sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now() atomicState.timeSendPush = now()
@@ -786,6 +822,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 +881,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) {
@@ -743,14 +740,21 @@ private void checkBridgeStatus() {
d.sendEvent(name: "idNumber", value: it.value.idNumber) d.sendEvent(name: "idNumber", value: it.value.idNumber)
} }
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")
} else {
state.bulbs?.each {
it.value.online = false
}
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false) d.sendEvent(name: "status", value: "Online")//setOnline(false)
} }
} }
} }
} }
def isValidSource(macAddress) { def isValidSource(macAddress) {
@@ -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)
} }
@@ -798,10 +801,12 @@ def parse(childDevice, description) {
} }
// Philips Hue priority for color is xy > ct > hs // Philips Hue priority for color is xy > ct > hs
// For SmartThings, try to always send hue, sat and hex
private sendColorEvents(device, xy, hue, sat, ct, colormode = null) { private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
if (device == null || (xy == null && hue == null && sat == null && ct == null)) if (device == null || (xy == null && hue == null && sat == null && ct == null))
return return
def events = [:]
// For now, only care about changing color temperature if requested by user // For now, only care about changing color temperature if requested by user
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) { if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below // for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
@@ -815,13 +820,13 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
if (hue != null) { if (hue != null) {
// 0-65535 // 0-65535
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
device.sendEvent([name: "hue", value: value, descriptionText: "Color has changed"]) events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
} }
if (sat != null) { if (sat != null) {
// 0-254 // 0-254
def value = Math.round(sat * 100 / 254) as int def value = Math.round(sat * 100 / 254) as int
device.sendEvent([name: "saturation", value: value, descriptionText: "Color has changed"]) events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
} }
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex // Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
@@ -833,17 +838,28 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
def model = state.bulbs[id]?.modelid def model = state.bulbs[id]?.modelid
def hex = colorFromXY(xy, model) def hex = colorFromXY(xy, model)
// TODO Disabled until a solution for the jumping color picker can be figured out // Create Hue and Saturation events if not previously existing
//device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: false]) def hsv = hexToHsv(hex)
if (events["hue"] == null)
events["hue"] = [name: "hue", value: hsv[0], descriptionText: "Color has changed", displayed: false]
if (events["saturation"] == null)
events["saturation"] = [name: "saturation", value: hsv[1], descriptionText: "Color has changed", displayed: false]
events["color"] = [name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: true]
} else if (colormode == "hs" || colormode == null) { } else if (colormode == "hs" || colormode == null) {
// colormode is "hs" or "xy" is missing, default to follow hue/sat which is already handled above // colormode is "hs" or "xy" is missing, default to follow hue/sat which is already handled above
def hueValue = (hue != null) ? events["hue"].value : Integer.parseInt("$device.currentHue")
def satValue = (sat != null) ? events["saturation"].value : Integer.parseInt("$device.currentSaturation")
// TODO Disabled until the standard behavior of lights is defined (hue and sat events are sent above)
//def hex = colorUtil.hslToHex((int) device.currentHue, (int) device.currentSaturation) def hex = hsvToHex(hueValue, satValue)
// device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed"]) events["color"] = [name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: true]
} }
return debug boolean sendColorChanged = false
events.each {
device.sendEvent(it.value)
}
} }
private sendBasicEvents(device, param, value) { private sendBasicEvents(device, param, value) {
@@ -883,36 +899,38 @@ private handleCommandResponse(body) {
// scan entire response before sending events to make sure they are always in the same order // scan entire response before sending events to make sure they are always in the same order
def updates = [:] def updates = [:]
body.each { payload -> body.each { payload ->
log.debug $payload
if (payload?.success) { if (payload?.success) {
def childDeviceNetworkId = app.id + "/" def childDeviceNetworkId = app.id + "/"
def eventType def eventType
payload.success.each { k, v -> payload.success.each { k, v ->
def data = k.split("/") def data = k.split("/")
if (data.length == 5) { if (data.length == 5) {
childDeviceNetworkId = app.id + "/" + k.split("/")[2] childDeviceNetworkId = app.id + "/" + k.split("/")[2]
if (!updates[childDeviceNetworkId]) if (!updates[childDeviceNetworkId])
updates[childDeviceNetworkId] = [:] updates[childDeviceNetworkId] = [:]
eventType = k.split("/")[4] eventType = k.split("/")[4]
updates[childDeviceNetworkId]."$eventType" = v updates[childDeviceNetworkId]."$eventType" = v
} }
} }
} else if (payload.error) { } else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}" log.warn "Error returned from Hue bridge error = ${body?.error}"
} }
} }
// 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)
sendBasicEvents(device, "on", params.on) def id = getId(device)
sendBasicEvents(device, "bri", params.bri) // If device is offline, then don't send events which will update device watch
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) if (isOnline(id)) {
} sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
}
return [] return []
} }
/** /**
* Handles a response to a poll (GET) sent to the Hue Bridge. * Handles a response to a poll (GET) sent to the Hue Bridge.
@@ -932,26 +950,33 @@ 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) {
sendBasicEvents(device, "on", bulb.value?.state?.on) if (state.bulbs[bulb.key]?.online == false) {
sendBasicEvents(device, "bri", bulb.value?.state?.bri) // light just came back online, notify device watch
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) 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, "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)
}
} 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)
} }
} }
return []
} }
return []
}
private updateInProgress() { private updateInProgress() {
state.updating = true state.updating = true
@@ -980,22 +1005,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 +1047,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)'"
updateInProgress() def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
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 = [:]
@@ -1059,26 +1112,22 @@ def setColor(childDevice, huesettings) {
def sat = null def sat = null
def xy = null def xy = null
// For now ignore model to get a consistent color if same color is set across multiple devices // Prefer hue/sat over hex to make sure it works with the majority of the smartapps
// def model = state.bulbs[getId(childDevice)]?.modelid if (huesettings.hue != null || huesettings.sat != null) {
if (huesettings.hex != null) { // If both hex and hue/sat are set, send all values to bridge to get hue/sat in response from bridge to
// generate hue/sat events even though bridge will prioritize XY when setting color
if (huesettings.hue != null)
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
} else if (huesettings.hex != null && false) {
// For now ignore model to get a consistent color if same color is set across multiple devices
// def model = state.bulbs[getId(childDevice)]?.modelid
// value.xy = calculateXY(huesettings.hex, model) // value.xy = calculateXY(huesettings.hex, model)
// Once groups, or scenes are introduced it might be a good idea to use unique models again // Once groups, or scenes are introduced it might be a good idea to use unique models again
value.xy = calculateXY(huesettings.hex) value.xy = calculateXY(huesettings.hex)
} }
// If both hex and hue/sat are set, send all values to bridge to get hue/sat in response from bridge to
// generate hue/sat events even though bridge will prioritize XY when setting color
if (huesettings.hue != null)
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
else
value.hue = Math.min(Math.round(childDevice.device?.currentValue("hue") * 65535 / 100), 65535)
if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
else
value.sat = Math.min(Math.round(childDevice.device?.currentValue("saturation") * 254 / 100), 254)
/* Disabled for now due to bad behavior via Lightning Wizard /* Disabled for now due to bad behavior via Lightning Wizard
if (!value.xy) { if (!value.xy) {
// Below will translate values to hex->XY to take into account the color support of the different hue types // Below will translate values to hex->XY to take into account the color support of the different hue types
@@ -1108,15 +1157,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]
} }
} }
@@ -1125,10 +1182,12 @@ private poll() {
def host = getBridgeIP() def host = getBridgeIP()
def uri = "/api/${state.username}/lights/" def uri = "/api/${state.username}/lights/"
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\r\n" +
HOST: ${host} "HOST: ${host}\r\n\r\n", 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) {
@@ -1140,13 +1199,11 @@ private put(path, body) {
log.debug "PUT: $host$uri" log.debug "PUT: $host$uri"
log.debug "BODY: ${bodyJSON}" log.debug "BODY: ${bodyJSON}"
sendHubCommand(new physicalgraph.device.HubAction("""PUT $uri HTTP/1.1 sendHubCommand(new physicalgraph.device.HubAction("PUT $uri HTTP/1.1\r\n" +
HOST: ${host} "HOST: ${host}\r\n" +
Content-Length: ${length} "Content-Length: ${length}\r\n" +
"\r\n" +
${bodyJSON} "${bodyJSON}", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
""", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
} }
/* /*
@@ -1198,7 +1255,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
} }
@@ -1604,3 +1661,101 @@ private boolean checkPointInLampsReach(p, colorPoints) {
return false; return false;
} }
} }
/**
* Converts an RGB color in hex to HSV.
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
*
* @param colorStr color value in hex (#ff03d3)
*
* @return HSV representation in an array (0-100) [hue, sat, value]
*/
def hexToHsv(colorStr){
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255
def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255
def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255;
def max = Math.max(Math.max(r, g), b)
def min = Math.min(Math.min(r, g), b)
def h, s, v = max;
def d = max - min;
s = max == 0 ? 0 : d / max;
if(max == min){
h = 0;
}else{
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [(h * 100).round(), (s * 100).round(), (v * 100).round()];
}
/**
* Converts HSV color to RGB in hex.
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
*
* @param hue hue 0-100
* @param sat saturation 0-100
* @param value value 0-100 (defaults to 100)
* @return the color in hex (#ff03d3)
*/
def hsvToHex(hue, sat, value = 100){
def r, g, b;
def h = hue / 100
def s = sat / 100
def v = value / 100
def i = Math.floor(h * 6);
def f = h * 6 - i;
def p = v * (1 - s);
def q = v * (1 - f * s);
def t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v
g = t
b = p
break
case 1:
r = q
g = v
b = p
break
case 2:
r = p
g = v
b = t
break
case 3:
r = p
g = q
b = v
break
case 4:
r = t
g = p
b = v
break
case 5:
r = v
g = p
b = q
break
}
// Converting float components to int components.
def r1 = String.format("%02X", (int) (r * 255.0f));
def g1 = String.format("%02X", (int) (g * 255.0f));
def b1 = String.format("%02X", (int) (b * 255.0f));
return "#$r1$g1$b1"
}

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

@@ -50,9 +50,9 @@ 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

@@ -51,7 +51,7 @@ definition(
} }
preferences(oauthPage: "deviceAuthorization") { preferences(oauthPage: "deviceAuthorization") {
page(name: "Credentials", title: "Connect to your Logitech Harmony device", content: "authPage", install: false, nextPage: "deviceAuthorization") page(name: "Credentials", title: "Connect to your Logitech Harmony device", content: "authPage", install: false, nextPage: "deviceAuthorization")
page(name: "deviceAuthorization", title: "Logitech Harmony device authorization", install: true) { page(name: "deviceAuthorization", title: "Logitech Harmony device authorization", install: true) {
section("Allow Logitech Harmony to control these things...") { section("Allow Logitech Harmony to control these things...") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
@@ -102,7 +102,8 @@ def authPage() {
description = "Click to enter Harmony Credentials" description = "Click to enter Harmony Credentials"
def redirectUrl = buildRedirectUrl def redirectUrl = buildRedirectUrl
return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) { return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) {
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description } section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description }
} }
} else { } else {
//device discovery request every 5 //25 seconds //device discovery request every 5 //25 seconds
@@ -314,8 +315,6 @@ def installed() {
} }
def updated() { def updated() {
unsubscribe()
unschedule()
if (!state.accessToken) { if (!state.accessToken) {
log.debug "About to create access token" log.debug "About to create access token"
createAccessToken() createAccessToken()

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

@@ -86,6 +86,7 @@ def firstPage()
def lightSwitchesDiscovered = lightSwitchesDiscovered() def lightSwitchesDiscovered = lightSwitchesDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
section("Select a device...") { section("Select a device...") {
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered

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 = """