Compare commits

...

125 Commits

Author SHA1 Message Date
Juan Pablo Risso
ce26a2941e Merge pull request #506 from juano2310/hue_bu
PROB-537 - Fix error in line 335
2016-02-12 14:34:00 -05:00
Juan Pablo Risso
81fb356a21 PROB-537 - Fix error in line 335 2016-02-12 14:31:04 -05:00
Vinay Rao
aadbcc2eee Merge pull request #501 from SmartThingsCommunity/staging
Rolling down changes from staging to master
2016-02-11 22:11:01 -08:00
Vinay Rao
4a49d4c15d Merge pull request #108 from natec007/MSA-68-1
MSA-68: Spruce Irrigation
2016-02-11 22:06:25 -08:00
Nathan Cauffman
0e3bd5aa74 MSA-68: Spruce Irrigation controller and soil moisture sensors.
Merge in bug fixes and renames to match with new repository layout.

Deleted spruce-controller.groovy from 'Spruce Irrigation'

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

Deleted spruce-scheduler.groovy from 'Spruce Irrigation'

Modifying 'Spruce Irrigation'

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

Modifying 'Spruce Irrigation'

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

Updated device types, start scheduler from device

Code review for ST publication

Review for ST submission

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

Moisture control adjsut

# This is the 2nd commit message:

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

Deleted spruce-sensor-v1.groovy from 'Spruce Irrigation'
2016-02-11 20:28:09 -08:00
Tyler Lange
a2e8a8303b Merge pull request #480 from rboy1/patch-1
Bugfixes for codeReports and AlarmReport (Rboy1)
2016-02-11 13:46:40 -08:00
Lars Finander
56580634e8 Merge pull request #494 from larsfinander/wemo
Fixed ArrayIndexOutOfBoundsException
2016-02-11 10:14:46 -08:00
Lars Finander
a82717744e DVCSMP-1480 Fixed ArrayIndexOutOfBoundsException
Fixed ArrayIndexOutOfBoundsException from events that lack values to some fields
in a few LAN Connect SmartApps.
2016-02-10 14:45:12 -08:00
Vinay Rao
b1096a67af Merge pull request #500 from SmartThingsCommunity/production
Rolling down changes from production to staging
2016-02-10 14:26:37 -08:00
Ben Boggess
ae945d1a15 Merge pull request #499 from boggebe/boggess/convert_hexToSignedInt_to_method
Convert closure to method
2016-02-10 11:16:13 -06:00
boggebe
12220da750 Convert closure to method 2016-02-10 10:57:10 -06:00
Ben Boggess
3f89bbb6d9 Merge pull request #498 from SmartThingsCommunity/revert-490-boggess/convert_hexToSignedInt_to_method
Revert "Convert closure to method"
2016-02-10 10:50:12 -06:00
Ben Boggess
fe2b36cd30 Revert "Convert closure to method" 2016-02-10 10:46:48 -06:00
Ben Boggess
55c17383e1 Merge pull request #490 from boggebe/boggess/convert_hexToSignedInt_to_method
Convert closure to method
2016-02-10 06:53:31 -06:00
boggebe
bd9a1d1dc5 Closure was causing sandbox issues locally 2016-02-08 16:03:01 -06:00
Vinay Rao
a6bec43f2a Merge pull request #474 from tpmanley/bugfix/multi_cmd_failure
multi: Fix occasional failures in sending ZigBee commands during configure
2016-02-02 23:04:58 -04:00
rboy1
6fe60146a4 Bugfixes for codeReports
1. Missing break in case 32 for AlarmReport
2. Bugfix - Not reporting all codes programmed into the lock causing SmartApps to fail while awaiting a codeReport notification (once a code is programmed into a lock it should be notified because there is no way to read back codes from the lock, apps awaiting a codeReport will go into an infinite programming loop to trying to update the codes and awaiting a codeReport notification indicating a successful programming)
2016-02-02 10:35:10 -05:00
Tom Manley
5ec82217ac multi: Fix occasional failures in sending ZigBee commands during configure
The missing delay after some of the commands was sometimes causing the
ZigBee command to fail.

Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1225
2016-01-29 14:41:12 -06:00
Kris Schaller
208f0d97df Merging staging into production 2016-01-25 13:12:05 -08:00
Kris Schaller
e444c8d020 Merging master into staging 2016-01-25 13:11:18 -08:00
Kristofer Schaller
3b89368d45 Merge pull request #467 from davidsulpy/master
New Initial State Event Streamer: removed buffering and sched-tasks
2016-01-25 13:08:51 -08:00
David Sulpy
12f6039de5 New Initial State Event Streamer: removed buffering and scheduled tasks for flushing buffer 2016-01-22 19:23:37 -06:00
Vinay Rao
65c9da32e7 Merge pull request #464 from workingmonk/smartpower_v1
[DVCSMP-1430] updating fingerprint for the problem device
2016-01-21 23:29:43 -08:00
Vinay Rao
7147770e2d updating fingerprint for the problem device 2016-01-21 18:20:03 -08:00
Vinay Rao
9f75bbfbde Merge pull request #463 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-20 17:13:25 -08:00
Vinay Rao
996ac27ba2 Merge pull request #462 from SmartThingsCommunity/master
Merging changes into stage
2016-01-20 17:06:44 -08:00
Jason Botello
5eb33eca19 Merge pull request #387 from SmartThingsCommunity/MSA-757-1
MSA-757: Timevalve Smart
2016-01-20 16:25:05 -08:00
Jason Botello
ec321ce85d Merge pull request #425 from hhhss91/MSA-786-1
MSA-786: Encored Technologies : Smart Energy Service
2016-01-20 16:24:46 -08:00
Steve Vlaminck
a20a58bd48 Merge pull request #458 from vlaminck/gentle-wake-up-controller
Added Gentle Wake Up controller, updated SmartApp
2016-01-20 14:03:12 -06:00
vlaminck
efabd07dea Added Gentle Wake Up controller, updated SmartApp 2016-01-20 09:05:47 -06:00
Vinay Rao
16b87c5eda Merge pull request #456 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-19 16:10:44 -08:00
Vinay Rao
05ad96d583 Merge pull request #455 from SmartThingsCommunity/master
Merging changes into stage
2016-01-19 16:08:42 -08:00
Tyler Lange
1e55f62048 Merge pull request #454 from workingmonk/deprecate_osram_flex
deprecating this DTH as the fingerprints have been moved to ZigBee RGBW
2016-01-19 16:06:54 -08:00
Vinay Rao
12b18eae08 deprecating this DTH as the fingerprints have been moved to ZigBee RGBW Bulb 2016-01-19 16:02:35 -08:00
Vinay Rao
0a6bb51974 Merge pull request #453 from SmartThingsCommunity/staging
Merging ZigBee RGBW into prod
2016-01-19 15:57:18 -08:00
Vinay Rao
9ab74c3ba6 Merge pull request #452 from SmartThingsCommunity/master
Merging ZigBee RGBW into staging
2016-01-19 15:56:38 -08:00
Tyler Lange
aad82e10ef Merge pull request #451 from workingmonk/zigbee_color_bulb
Adding ZigBee RGBW DTH - generic ZigBee DTH for OSRAM Flex Strip, OSRAM A19 RGBW, OSRAM BR30 RGBW, OSRAM RT5/6
2016-01-19 15:55:07 -08:00
Vinay Rao
b11b362c60 Adding ZigBee RGBW DTH 2016-01-19 15:40:43 -08:00
Tom Manley
8f08a0819c Merge pull request #449 from SmartThingsCommunity/staging
Merge staging into production
2016-01-19 14:25:26 -06:00
Tom Manley
d34d1d3615 Merge pull request #448 from SmartThingsCommunity/master
Merge master into staging
2016-01-19 13:46:27 -06:00
Tom Manley
e1853b8e50 Merge pull request #443 from tpmanley/feature/sensor_updates
Feature/sensor updates
2016-01-19 13:43:40 -06:00
Tom Manley
968834e33e Use table for battery voltage to percent remaining calculation
The new table based approach yields a more accurate battery percentage
remaining than the old linear calculation.

Resolves:
    https://smartthings.atlassian.net/browse/SMJN-39
2016-01-19 13:40:02 -06:00
Tom Manley
e51a38eb28 Fix whitespace issues - no code changes
Replaced spaces with tabs for indentation and removed some unnecessary
white space.
2016-01-18 11:00:37 -06:00
Tom Manley
553b45a3f3 Merge pull request #441 from tpmanley/bugfix/arrival_readattr_events
arrival: Keep 'read attr' messages from being turned into events
2016-01-15 09:51:39 -06:00
Tom Manley
d9ab3bca00 arrival: Keep 'read attr' messages from being turned into events
Previously parse was returning null which causes the platform to create an event
using the message passed to parse. We don't want that to happen so return
an empty list instead.

Resolves:
    https://smartthings.atlassian.net/browse/SMJN-38
2016-01-14 15:34:08 -06:00
Tom Manley
5085d7f184 Merge pull request #433 from SmartThingsCommunity/staging
Merge staging into production
2016-01-13 12:56:38 -06:00
Juan Pablo Risso
dc927e0659 Merge pull request #434 from SmartThingsCommunity/master
Master -> Staging
2016-01-13 13:21:51 -05:00
Juan Pablo Risso
56cfe9e936 Merge pull request #430 from juano2310/Wemo
PROB-537 - Fix error in line 337
2016-01-13 13:20:10 -05:00
Tom Manley
991637b1c1 Merge pull request #432 from SmartThingsCommunity/master
Merge from master into staging
2016-01-13 10:39:39 -06:00
Matt Pennig
d69abb64bd Merge pull request #385 from SmartThingsCommunity/rich-simulated-thermostat
Adding multiAttributeTile definition to Simulated Thermostat device type handler
2016-01-11 15:10:59 -06:00
Tom Manley
7429ecc83b Merge pull request #423 from tpmanley/feature/arrival_sensor_ha
arrival: Add support for ZigBee HA arrival sensor
2016-01-11 12:42:44 -06:00
Tom Manley
112a35f5db arrival: Change voltage range for battery remaining calculation 2016-01-11 12:41:50 -06:00
Juan Risso
0d214b742e PROB-537 - Fix error in line 337 2016-01-11 12:10:13 -05:00
Juan Pablo Risso
1372d4005a Merge pull request #427 from SmartThingsCommunity/master
Master -> Staging
2016-01-08 16:08:16 -05:00
Juan Pablo Risso
c297564665 Merge pull request #426 from juano2310/Wemo
Increased delay to mark device as offline
2016-01-08 16:06:38 -05:00
Juan Risso
26ab32565b Increased delay to mark device as offline 2016-01-08 16:01:57 -05:00
hyeon seok yang
c20026b376 MSA-786: Description from the app : "With visible realtime energy usage status, have good energy habits and enrich your life"
This app is specialized to show energy data which was grabbed from Encored Technologies' device that user installed at their home.
2016-01-08 16:01:05 +09:00
Tom Manley
9733947fea arrival: Add support for ZigBee HA arrival sensor
Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1305
    https://smartthings.atlassian.net/browse/DVCSMP-1322
2016-01-07 14:30:04 -06:00
Yaima
9439efd7b9 Merge pull request #421 from SmartThingsCommunity/master
Merging Ecobee and SmartSense code
2016-01-06 15:34:01 -08:00
Yaima
6abf8c7f20 Merge pull request #420 from Yaima/master
Removed degree sign from tile
2016-01-06 15:25:19 -08:00
Yaima Valdivia
fe505ddc9f Removed degree sign from tile
https://smartthings.atlassian.net/browse/DVCSMP-1318
2016-01-06 15:24:47 -08:00
Tom Manley
f4034f5ccf Merge pull request #419 from tpmanley/bugfix/multi_adjust_threshold
multi: Adjust threshold multiplier to prevent motion detection while …
2016-01-06 14:18:42 -06:00
Tom Manley
c1c2431299 multi: Adjust threshold multiplier to prevent motion detection while idle
The previous threshold multiplier was found to be too low so motion was
being detected while the sensor was idle. This new value of 630 seems to
produce better results.
2016-01-06 13:27:48 -06:00
Tom Manley
39f0c49ea6 Merge pull request #411 from tpmanley/bugfix/multi_accel_parsing
multi: Fix x,y,z parsing error
2016-01-06 13:26:27 -06:00
Yaima
70b8a042a7 Merge pull request #415 from SmartThingsCommunity/staging
Merging to prod
2016-01-05 16:01:55 -08:00
Yaima
451b7a4923 Merge pull request #414 from SmartThingsCommunity/master
Merging to staging
2016-01-05 16:01:05 -08:00
Yaima
ed5a409c63 Merge pull request #413 from Yaima/master
Added canChangeIcon: true
2016-01-05 16:00:08 -08:00
Yaima Valdivia
8453292038 Added canChangeIcon: true 2016-01-05 15:59:14 -08:00
Tom Manley
e98a04a1b4 multi: Fix x,y,z parsing error
The previous axis value parsing code was very fragile. For example, this
code block:

    def unsignedY = hexToInt(part.split("13")[1].trim())

would fail when `part` was "13xx13", where "xx" is any value. The split
assumed the value "13" was present only once in the string, and everything
after the "13" was the value. When "13" was part of the value this code
would interpret only "xx" as the value, instead of "xx13".

The new parsing code is not fragile like this. It knows exactly what bytes
of the string are X, Y, and Z and parses the values correctly.
2016-01-04 15:21:55 -06:00
Kris Schaller
22c810e9df Merging staging into production 2016-01-04 03:43:46 -08:00
Kris Schaller
cc80373b89 Merging master into staging 2016-01-04 03:43:30 -08:00
Kristofer Schaller
41e95b9248 Merge pull request #400 from kris-schaller/phonebook_auto_unlock
Updating code to use the phonebook API
2016-01-04 03:41:50 -08:00
Tom Manley
63f20c912d Merge pull request #407 from tpmanley/bugfix/multi_invalid_XY
multi: ignore attribute reports that don't include all three axis
2015-12-31 14:42:03 -06:00
Tom Manley
837d2d0cfd multi: ignore attribute reports that don't include all three axis
Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1366
2015-12-31 11:13:41 -06:00
Kris Schaller
629c4cc231 Updating code to use the phonebook API 2015-12-30 14:14:37 -08:00
Kris Schaller
ea98194abf Merging staging into production 2015-12-28 16:21:37 -08:00
Kris Schaller
94f11c583f Merging master into staging 2015-12-28 16:21:17 -08:00
Kristofer Schaller
f12684565c Merge pull request #399 from kris-schaller/vinli_icon
Changing icon paths to hosted files
2015-12-28 16:18:48 -08:00
Kris Schaller
51e727b91a Changing icon paths to hosted files 2015-12-28 16:06:15 -08:00
Kris Schaller
4a6e000cee Merging staging into production 2015-12-28 15:41:51 -08:00
Kris Schaller
c3cf4089f4 Merging master into staging 2015-12-28 15:41:29 -08:00
Kristofer Schaller
49a858eb5c Merge pull request #398 from kris-schaller/vinli_icon
Adding vinli icons
2015-12-28 15:39:16 -08:00
Kris Schaller
112a4087b0 Adding vinli icons 2015-12-28 15:38:23 -08:00
Kris Schaller
71f9f19465 Merging staging into production 2015-12-28 10:31:40 -08:00
Kris Schaller
f8317a6d2c Merging master into staging 2015-12-28 10:31:20 -08:00
Kristofer Schaller
132d8fc9d8 Merge pull request #302 from SmartThingsCommunity/MSA-699-1
Merged publication request 'Vinli Home Connect'
2015-12-28 10:29:56 -08:00
Matt Pennig
358cf261e8 Adding multiAttributeTile definition to Simulated Thermostat device type handler 2015-12-22 11:14:53 -06:00
Kris Schaller
2edda411e2 Merging staging into production 2015-12-21 12:55:20 -08:00
Kris Schaller
dd19b6e73f Merging master into staging 2015-12-21 12:55:03 -08:00
Daniel
21041570db Modifying 'Vinli Home Connect' 2015-12-17 09:40:00 -06:00
Kris Schaller
ac74c6126d Merging staging into production 2015-12-15 09:38:16 -08:00
Kris Schaller
80e02416f3 Merging master into staging 2015-12-15 09:38:02 -08:00
Kris Schaller
653f0e28ca Merging staging into production 2015-12-14 17:25:47 -08:00
Kris Schaller
49dc1e96ba Merging master into staging 2015-12-14 17:25:34 -08:00
Kris Schaller
76fcca90d3 Merging staging into production 2015-12-14 17:09:08 -08:00
Kris Schaller
c197f4263e Merging master into staging 2015-12-14 17:08:51 -08:00
Kris Schaller
54f976229e Merging staging into production 2015-12-14 12:59:39 -08:00
Kris Schaller
3ba148d5d2 Merging master into staging 2015-12-14 12:59:25 -08:00
Kris Schaller
041733373b Merging staging into production 2015-12-10 15:09:04 -08:00
Kris Schaller
b67783a235 Merging master into staging 2015-12-10 15:08:28 -08:00
bflorian
b90e2a1982 Merge branch 'staging' into production 2015-12-05 10:26:47 -05:00
bflorian
ae444dfe09 Merge branch 'staging' of https://github.com/SmartThingsCommunity/SmartThingsPublic into staging 2015-12-05 10:26:25 -05:00
Donald C. Kirker
df55116ac6 Merge pull request #330 from mckeed/prob-398
PROB-398 Fix Homeseer Multi Instance encap parse
2015-12-04 12:38:39 -08:00
Vinay Rao
7d0b3ef796 Merge pull request #333 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-12-02 15:51:30 -08:00
Vinay Rao
c3d5f60250 Merge pull request #332 from SmartThingsCommunity/master
Merging changes into stage
2015-12-02 15:49:34 -08:00
Duncan McKee
f55ea8b2f8 Fix Homeseer Multi Instance encap parse PROB-398 2015-12-02 11:49:13 -06:00
Juan Pablo Risso
968c9cc647 Merge pull request #317 from SmartThingsCommunity/staging
Staging -> Production
2015-11-26 11:09:51 -05:00
Juan Pablo Risso
3e7ce67ea4 Merge pull request #316 from SmartThingsCommunity/master
Master -> Staging
2015-11-26 11:08:44 -05:00
Daniel
96f2c5ed8b MSA-699: Vinli Home Connect allows users to control their Smartthings Devices with their Vinli connect vehicles. The Vinli device is an OBD dongle that can report when it leaves or enters geofences. A user can, for instance, set their doors to lock and lights to turn off when they leave proximity to their home. 2015-11-23 11:38:25 -06:00
Vinay Rao
479651a330 Merge pull request #296 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-11-18 14:36:36 -08:00
Vinay Rao
39b00c2a04 Merge pull request #295 from SmartThingsCommunity/master
Merging changes into stage
2015-11-18 14:04:44 -08:00
Vinay Rao
5a52e69911 Merge remote-tracking branch 'origin/staging' into production 2015-11-13 17:10:15 -08:00
Vinay Rao
7f4384cd85 Merge remote-tracking branch 'origin/master' into staging 2015-11-13 17:09:47 -08:00
Vinay Rao
42479a94b4 Merge remote-tracking branch 'origin/staging' into production 2015-11-12 15:30:24 -08:00
Juan Pablo Risso
57e668f1f2 Merge pull request #268 from SmartThingsCommunity/master
Master -> Staging
2015-11-09 16:31:21 -05:00
Warodom Khamphanchai
0321a7f071 Merge pull request #135 from kwarodom/fibaroSmokeSensor
Fibaro Smoke Sensor: initial device type
2015-10-20 10:53:21 -07:00
Vinay Rao
855ed02ffa Merge remote-tracking branch 'origin/staging' into production 2015-10-15 11:55:46 -07:00
Vinay Rao
60fd008d4a Merge remote-tracking branch 'origin/master' into staging 2015-10-15 11:45:09 -07:00
Kris Schaller
df764d57c3 Merging staging into production 2015-10-13 16:15:40 -07:00
Kris Schaller
a96bb027c8 Merging master into staging 2015-10-13 16:15:28 -07:00
Vinay Rao
6b62f88bb7 Merge remote-tracking branch 'origin/master' into production 2015-10-06 14:13:30 -05:00
Vinay Rao
848bbdcf2b Merge remote-tracking branch 'origin/master' into staging 2015-10-06 13:58:55 -05:00
bflorian
12288accda Merge branch 'master' into staging 2015-10-02 07:21:38 -07:00
Juan Pablo Risso
3533943827 Merge pull request #161 from SmartThingsCommunity/master
Merge Master -> Staging
2015-10-01 11:02:20 -04:00
30 changed files with 5751 additions and 741 deletions

View File

@@ -0,0 +1,104 @@
/**
* EnerTalk Energy Meter
*
* Copyright 2015 hyeon seok yang
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "EnerTalk Energy Meter", namespace: "Encored Technologies", author: "hyeon seok yang") {
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale:2) {
valueTile("view", "device.view", decoration: "flat") {
state "view", label:' ${currentValue} kWh'
}
valueTile("month", "device.month", width: 6, height : 3, decoration: "flat") {
state "month", label:' ${currentValue}'
}
valueTile("real", "device.real", width: 2, height : 2, decoration: "flat") {
state "real", label:' ${currentValue}'
}
valueTile("tier", "device.tier", width: 2, height : 2, decoration: "flat") {
state "tier", label:' ${currentValue}'
}
valueTile("plan", "device.plan", width: 2, height : 2, decoration: "flat") {
state "plan", label:' ${currentValue}'
}
htmlTile(name:"deepLink", action:"linkApp", whitelist:["code.jquery.com",
"ajax.googleapis.com",
"fonts.googleapis.com",
"code.highcharts.com",
"enertalk-card.encoredtech.com",
"s3-ap-northeast-1.amazonaws.com",
"s3.amazonaws.com",
"ui-hub.encoredtech.com",
"enertalk-auth.encoredtech.com",
"api.encoredtech.com",
"cdnjs.cloudflare.com",
"encoredtech.com",
"itunes.apple.com"], width:2, height:2){}
main (["view"])
details (["month", "real", "tier", "plan", "deepLink"])
}
}
mappings {
path("/linkApp") {action: [ GET: "getLinkedApp" ]}
}
def getLinkedApp() {
def lang = clientLocale?.language
if ("${lang}" == "ko") {
lang = "<p style=\'margin-left:15vw; color: #aeaeb0;\'>기기 설정</p>"
} else {
lang = "<p style=\'margin-left:5vw; color: #aeaeb0;\'>Setup Device</p>"
}
renderHTML() {
head {
"""
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, height=device-height">
<style>
#레이어_1 { margin-left : 17vw; width : 50vw; height : 50vw;}
.st0{fill:#B5B6BB;}
</style>
"""
}
body {
"""
<div id="container">
<a id="st-deep-link" href="#">
<svg version="1.1" id="레이어_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"><path class="st0" d="M20,0C9,0,0,9,0,20C0,30.5,8,39,18.2,40l3.8-4.8l-3.9-4.8c-4.9-0.9-8.6-5.2-8.6-10.4c0-5.8,4.7-10.5,10.5-10.5
S30.5,14.2,30.5,20c0,5.1-3.7,9.4-8.5,10.3l3.7,4.5L21.8,40C32,39.1,40,30.5,40,20C40,9,31,0,20,0z"/></svg>
</a>
${lang}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1;
if(!isAndroid) {
\$("#st-deep-link").attr("href", "https://itunes.apple.com/kr/app/enertalk-for-home/id1024660780?mt=8");
} else {
\$("#st-deep-link").attr("href", "market://details?id=com.ionicframework.enertalkhome874425");
}
</script>
"""
}
}
}

View File

@@ -0,0 +1,627 @@
/**
* Spruce Controller - Pre Release V2 10/11/2015
*
* Copyright 2015 Plaid Systems
*
* Author: NC
* Date: 2015-11
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
-----------V3 updates-11-2015------------
-Start program button updated to signal schedule check in Scheduler
11/17 alarm "0" -> 0 (ln 305)
*/
metadata {
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
capability "Switch"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Valve"
attribute "switch", "string"
attribute "switch1", "string"
attribute "switch2", "string"
attribute "switch8", "string"
attribute "switch5", "string"
attribute "switch3", "string"
attribute "switch4", "string"
attribute "switch6", "string"
attribute "switch7", "string"
attribute "switch9", "string"
attribute "switch10", "string"
attribute "switch11", "string"
attribute "switch12", "string"
attribute "switch13", "string"
attribute "switch14", "string"
attribute "switch15", "string"
attribute "switch16", "string"
attribute "status", "string"
command "programOn"
command "programOff"
command "on"
command "off"
command "z1on"
command "z1off"
command "z2on"
command "z2off"
command "z3on"
command "z3off"
command "z4on"
command "z4off"
command "z5on"
command "z5off"
command "z6on"
command "z6off"
command "z7on"
command "z7off"
command "z8on"
command "z8off"
command "z9on"
command "z9off"
command "z10on"
command "z10off"
command "z11on"
command "z11off"
command "z12on"
command "z12off"
command "z13on"
command "z13off"
command "z14on"
command "z14off"
command "z15on"
command "z15off"
command "z16on"
command "z16off"
command "offtime"
command "refresh"
command "rain"
command "manual"
command "setDisplay"
command "settingsMap"
command "writeTime"
command "writeType"
command "notify"
command "updated"
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
}
// simulator metadata
simulator {
// status messages
// reply messages
}
preferences {
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
}
// UI tile definitions
tiles {
standardTile("status", "device.status") {
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
}
standardTile("switch", "device.switch") {
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
}
standardTile("rainsensor", "device.rainsensor") {
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
}
standardTile("switch1", "device.switch1") {
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch2", "device.switch2") {
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch3", "device.switch3", inactiveLabel: false) {
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch4", "device.switch4", inactiveLabel: false) {
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch5", "device.switch5", inactiveLabel: false) {
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch6", "device.switch6", inactiveLabel: false) {
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch7", "device.switch7", inactiveLabel: false) {
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch8", "device.switch8", inactiveLabel: false) {
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch9", "device.switch9", inactiveLabel: false) {
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch10", "device.switch10", inactiveLabel: false) {
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch11", "device.switch11", inactiveLabel: false) {
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch12", "device.switch12", inactiveLabel: false) {
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch13", "device.switch13", inactiveLabel: false) {
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch14", "device.switch14", inactiveLabel: false) {
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch15", "device.switch15", inactiveLabel: false) {
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch16", "device.switch16", inactiveLabel: false) {
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", action: "refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main (["status"])
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
}
}
def programOn(){
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
}
def programOff(){
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
off()
}
def updated(){
log.debug "updated"
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.debug "Parse description $description"
def result = null
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
//log.debug "Desc Map: $descMap"
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
log.debug "Zone"
map = getZone(descMap)
}
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
log.debug "Alarm"
map = getAlarm(descMap)
}
}
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map $result"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getZone(descMap){
def map = [:]
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
String onoff
if(descMap.value == "00"){
onoff = "off"
}
else onoff = "on"
if (EP == 1){
map.name = "switch"
map.value = onoff
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
}
else if (EP == 18) {
map.name = "rainsensor"
map.value = "rainSensor" + onoff
map.descriptionText = "${device.displayName} rain sensor is $onoff"
}
else {
EP -= 1
map.name = "switch" + EP
map.value = "z" + EP + onoff
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
}
map.isStateChange = true
map.displayed = true
return map
}
def getAlarm(descMap){
def map = [:]
map.name = "status"
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
log.debug "${alarmID}"
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
map.value = "alarm"
map.isStateChange = true
map.displayed = true
return map
}
//status notify and change status
def notify(value, text){
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
}
//prefrences - rain sensor, manual time
def rain() {
log.debug "Rain $RainEnable"
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
}
def manual(){
log.debug "Time $ManualTime"
def mTime = 10
if (ManualTime) mTime = ManualTime
def manualTime = hex(mTime)
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
}
//write switch time settings map
def settingsMap(WriteTimes, attrType){
log.debug WriteTimes
def i = 1
def runTime
def sendCmds = []
while(i <= 17){
if (WriteTimes."${i}"){
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
log.debug "${i} : $runTime"
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
sendCmds.push("delay 500")
}
i++
}
return sendCmds
}
//send switch time
def writeType(wEP, cycle){
log.debug "wt ${wEP} ${cycle}"
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
}
//send switch off time
def writeTime(wEP, runTime){
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
}
//set reporting and binding
def configure() {
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
def configCmds = [
//program on/off
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//zones 1-8
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//zones 9-16
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//rain sensor
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + rain() + manual()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}
def refresh() {
log.debug "refresh"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
]
return refreshCmds + rain() + manual()
}
// Commands to device
//zones on - 8
def on() {
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
log.debug "on"
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
}
def off() {
log.debug "off"
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
}
def z1on() {
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
}
def z1off() {
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
}
def z2on() {
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
}
def z2off() {
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
}
def z3on() {
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
}
def z3off() {
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
}
def z4on() {
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
}
def z4off() {
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
}
def z5on() {
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
}
def z5off() {
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
}
def z6on() {
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
}
def z6off() {
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
}
def z7on() {
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
}
def z7off() {
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
}
def z8on() {
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
}
def z8off() {
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
}
//zones 9 - 16
def z9on() {
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
}
def z9off() {
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
}
def z10on() {
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
}
def z10off() {
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
}
def z11on() {
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
}
def z11off() {
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
}
def z12on() {
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
}
def z12off() {
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
}
def z13on() {
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
}
def z13off() {
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
}
def z14on() {
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
}
def z14off() {
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
}
def z15on() {
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
}
def z15off() {
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
}
def z16on() {
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
}
def z16off() {
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
}

View File

@@ -0,0 +1,397 @@
/**
* Spruce Sensor -Pre-release V2 10/8/2015
*
* Copyright 2014 Plaid Systems
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
-------10/20/2015 Updates--------
-Fix/add battery reporting interval to update
-remove polling and/or refresh(?)
*/
metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
capability "Configuration"
capability "Battery"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"
capability "Sensor"
//capability "Polling"
attribute "maxHum", "string"
attribute "minHum", "string"
command "resetHumidity"
command "refresh"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph", title: ""
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
}
tiles {
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: false) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
state "humidity", label:'${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
state "maxHum", label:'High ${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
state "minHum", label:'Low ${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
state "battery", label:'${currentValue}% battery'
}
main (["humidity"])
details(["humidity","maxHum","minHum","temperature","battery"])
}
}
def parse(String description) {
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
map = parseCustomMessage(description)
}
def result = map ? createEvent(map) : null
//check in configuration change
if (!device.latestValue('configuration')) result = poll()
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
result = poll()
}
log.debug "result: $result"
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def linkText = getLinkText(device)
//log.debug "Catchall"
def descMap = zigbee.parse(description)
//check humidity configuration is complete
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
def configInterval = 10
if (interval != null) configInterval = interval
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
//setConfig()
log.debug "config complete"
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
}
else if (descMap.command == 0x0001){
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
def intString = Integer.parseInt(hexString, 16)
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
if (descMap.clusterId == 0x0402){
def value = getTemperature(hexString)
resultMap = getTemperatureResult(value)
}
else if (descMap.clusterId == 0x0405){
def value = Math.round(new BigDecimal(intString / 100)).toString()
resultMap = getHumidityResult(value)
}
else return null
}
else return null
return resultMap
}
private Map parseReportAttributeMessage(String description) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
log.debug "Report Attributes"
Map resultMap = [:]
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
resultMap = getBatteryResult(descMap.value)
}
return resultMap
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
log.debug "parseCustom"
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
else if (description?.startsWith('humidity: ')) {
def pct = (description - "humidity: " - "%").trim()
if (pct.isNumber()) {
def value = Math.round(new BigDecimal(pct)).toString()
resultMap = getHumidityResult(value)
} else {
log.error "invalid humidity: ${pct}"
}
}
return resultMap
}
private Map getHumidityResult(value) {
def linkText = getLinkText(device)
def maxHumValue = 0
def minHumValue = 0
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
def compare = value.toInteger()
if (compare > maxHumValue) {
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
}
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
}
return [
name: 'humidity',
value: value,
unit: '%',
descriptionText: "${linkText} soil moisture is ${value}%"
]
}
def getTemperature(value) {
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
//log.debug "Report Temp $value : $celsius C"
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getTemperatureResult(value) {
log.debug "Temperature: $value"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getBatteryResult(value) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def min = 2500
def percent = ((Integer.parseInt(value, 16) - min) / 5)
percent = Math.max(0, Math.min(percent, 100.0))
result.value = Math.round(percent)
def descriptionText
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
else result.descriptionText = "${linkText} battery is ${result.value}%"
return result
}
def resetHumidity(){
def linkText = getLinkText(device)
def minHumValue = 0
def maxHumValue = 0
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
}
def setConfig(){
def configInterval = 100
if (interval != null) configInterval = interval
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
}
//when device preferences are changed
def updated(){
log.debug "device updated"
if (!device.latestValue('configuration')) configure()
else{
if (resetMinMax == true) resetHumidity()
if (device.latestValue('configuration').toInteger() != interval && interval != null){
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
}
}
}
//poll
def poll() {
log.debug "poll called"
List cmds = []
if (!device.latestValue('configuration')) cmds += configure()
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
cmds += intervalUpdate()
}
//cmds += refresh()
log.debug "commands $cmds"
return cmds?.collect { new physicalgraph.device.HubAction(it) }
}
//update intervals
def intervalUpdate(){
log.debug "intervalUpdate"
def minReport = 10
def maxReport = 610
if (interval != null) {
minReport = interval
maxReport = interval * 61
}
[
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
}
def refresh() {
log.debug "refresh"
[
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 1 0"
]
}
//configure
def configure() {
//set minReport = measurement in minutes
def minReport = 10
def maxReport = 610
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
//temperature
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
//min = soil measure interval
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
//min = battery measure interval 1 = 1 hour
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
] + refresh()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
capability "Actuator"
capability "Presence Sensor"
capability "Sensor"
capability "Battery"
capability "Configuration"
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png"
])
}
section {
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
}
}
tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
standardTile("beep", "device.beep", decoration: "flat") {
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
main "presence"
details(["presence", "beep", "battery"])
}
}
def updated() {
startTimer()
}
def configure() {
def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01)
log.debug "configure -- cmds: ${cmds}"
return cmds
}
def beep() {
log.debug "Sending Identify command to beep the sensor for 5 seconds"
return zigbee.command(0x0003, 0x00, "0500")
}
def parse(String description) {
state.lastCheckin = now()
handlePresenceEvent(true)
if (description?.startsWith('read attr -')) {
handleReportAttributeMessage(description)
}
return []
}
private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
}
}
/**
* Create battery event from reported battery voltage.
*
* @param volts Battery voltage in .1V increments
*/
private handleBatteryEvent(volts) {
if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
}
else {
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
def linkText = getLinkText(device)
def eventMap = [
name: 'battery',
value: pct,
descriptionText: "${linkText} battery was ${pct}%"
]
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
sendEvent(eventMap)
}
}
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
log.debug "Sensor is present"
startTimer()
} else if (!present) {
log.debug "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
log.debug "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
log.debug "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
log.debug "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}

View File

@@ -67,7 +67,7 @@ metadata {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
}
valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") {
state "thermostatSetpoint", label:'${currentValue}°'
state "thermostatSetpoint", label:'${currentValue}'
}
valueTile("currentStatus", "device.thermostatStatus", height: 1, width: 2, decoration: "flat") {
state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff"

View File

@@ -0,0 +1,126 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Gentle Wake Up Controller", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Timed Session"
attribute "percentComplete", "number"
command "setPercentComplete", ["number"]
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name: "richTile", type:"generic", width:6, height:4) {
tileAttribute("sessionStatus", key: "PRIMARY_CONTROL") {
attributeState "cancelled", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "running"
attributeState "stopped", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "cancelled"
attributeState "running", action: "timed session.stop", icon: "http://f.cl.ly/items/0B3y3p2V3X2l3P3y3W09/stopbutton.png", backgroundColor: "#79b821", nextState: "cancelled"
}
tileAttribute("timeRemaining", key: "SECONDARY_CONTROL") {
attributeState "timeRemaining", label:'${currentValue} remaining'
}
tileAttribute("percentComplete", key: "SLIDER_CONTROL") {
attributeState "percentComplete", action: "timed session.setTimeRemaining"
}
}
// start/stop
standardTile("sessionStatusTile", "sessionStatus", width: 1, height: 1, canChangeIcon: true) {
state "cancelled", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
state "stopped", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
state "running", label: "Running", action: "timed session.stop", backgroundColor: "#79b821", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
}
// duration
valueTile("timeRemainingTile", "timeRemaining", decoration: "flat", width: 2) {
state "timeRemaining", label:'${currentValue} left'
}
controlTile("percentCompleteTile", "percentComplete", "slider", height: 1, width: 3) {
state "percentComplete", action: "timed session.setTimeRemaining"
}
main "sessionStatusTile"
details "richTile"
// details(["richTile", "sessionStatusTile", "timeRemainingTile", "percentCompleteTile"])
}
}
// parse events into attributes
def parse(description) {
log.debug "Parsing '${description}'"
// TODO: handle 'switch' attribute
// TODO: handle 'level' attribute
// TODO: handle 'sessionStatus' attribute
// TODO: handle 'timeRemaining' attribute
}
// handle commands
def on() {
log.debug "Executing 'on'"
startDimming()
}
def off() {
log.debug "Executing 'off'"
stopDimming()
}
def setTimeRemaining(percentComplete) {
log.debug "Executing 'setTimeRemaining' to ${percentComplete}% complete"
parent.jumpTo(percentComplete)
}
def start() {
log.debug "Executing 'start'"
startDimming()
}
def stop() {
log.debug "Executing 'stop'"
stopDimming()
}
def pause() {
log.debug "Executing 'pause'"
// TODO: handle 'pause' command
}
def cancel() {
log.debug "Executing 'cancel'"
stopDimming()
}
def startDimming() {
log.trace "startDimming"
log.debug "parent: ${parent}"
parent.start("controller")
}
def stopDimming() {
log.trace "stopDimming"
log.debug "parent: ${parent}"
parent.stop("controller")
}
def controllerEvent(eventData) {
log.trace "controllerEvent"
sendEvent(eventData)
}

View File

@@ -80,19 +80,12 @@ def parse(String description) {
if (cmd) {
result = zwaveEvent(cmd)
}
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
def encapsulated = null
if (cmd.respondsTo("encapsulatedCommand")) {
encapsulated = cmd.encapsulatedCommand()
} else {
def hex1 = { n -> String.format("%02X", n) }
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
}
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
return encapsulated ? zwaveEvent(encapsulated) : null
}

View File

@@ -4,6 +4,7 @@
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
@@ -23,8 +24,8 @@ metadata {
command "setAdjustedColor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
}

View File

@@ -5,7 +5,7 @@ metadata {
capability "Switch"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
}
// simulator metadata

View File

@@ -20,8 +20,8 @@ metadata {
capability "Refresh"
capability "Temperature Measurement"
capability "Water Sensor"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
@@ -29,9 +29,9 @@ metadata {
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
}
simulator {
}
preferences {
@@ -47,7 +47,7 @@ metadata {
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
tiles(scale: 2) {
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
@@ -78,7 +78,7 @@ metadata {
details(["water", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
@@ -92,59 +92,59 @@ def parse(String description) {
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -153,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -167,42 +167,42 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
case '0x0022': // Tamper Alarm
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0023': // Battery Alarm
break
case '0x0022': // Tamper Alarm
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0023': // Battery Alarm
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0026': // Trouble/Failure
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
@@ -215,24 +215,47 @@ def getTemperature(value) {
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def result = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
if (rawValue == 0 || rawValue == 255) {}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
}
return result
@@ -267,7 +290,7 @@ private Map getMoistureResult(value) {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
@@ -277,32 +300,32 @@ def refresh() {
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"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",
"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"
"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
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
]
}
private getEndpointId() {
@@ -314,19 +337,19 @@ private hex(value) {
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -19,17 +19,17 @@ metadata {
capability "Motion Sensor"
capability "Configuration"
capability "Battery"
capability "Temperature Measurement"
capability "Temperature Measurement"
capability "Refresh"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
}
simulator {
@@ -85,7 +85,7 @@ metadata {
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -96,55 +96,55 @@ def parse(String description) {
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0406:
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
return resultMap
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
@@ -153,7 +153,7 @@ private Map parseReportAttributeMessage(String description) {
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -162,14 +162,14 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -180,44 +180,44 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0023': // Battery Alarm
break
case '0x0025': // Restore Report
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0025': // Restore Report
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
@@ -230,30 +230,46 @@ def getTemperature(value) {
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
log.debug rawValue
def result = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {}
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else if (volts > 0){
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
}
@@ -338,19 +354,19 @@ private hex(value) {
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -14,28 +14,28 @@
*
*/
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
capability "Battery"
capability "Configuration"
capability "Sensor"
capability "Configuration"
capability "Sensor"
capability "Contact Sensor"
capability "Acceleration Sensor"
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
attribute "status", "string"
}
}
simulator {
simulator {
status "open": "zone report :: type: 19 value: 0031"
status "closed": "zone report :: type: 19 value: 0030"
@@ -52,7 +52,7 @@
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0"
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000"
}
preferences {
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
@@ -62,13 +62,13 @@
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
}
}
tiles(scale: 2) {
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
@@ -106,9 +106,9 @@
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["status", "acceleration", "temperature"])
@@ -121,61 +121,61 @@ def parse(String description) {
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('temperature: ')) {
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
else if (description?.startsWith('read attr -')) {
result = parseReportAttributeMessage(description).each { createEvent(it) }
}
else if (description?.startsWith('read attr -')) {
result = parseReportAttributeMessage(description).each { createEvent(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0xFC02:
log.debug 'ACCELERATION'
break
case 0xFC02:
log.debug 'ACCELERATION'
break
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private List parseReportAttributeMessage(String description) {
@@ -202,10 +202,12 @@ private List parseReportAttributeMessage(String description) {
result << parseAxis(threeAxisAttributes)
descMap.value = descMap.value[-2..-1]
}
result << getAccelerationResult(descMap.value)
result << getAccelerationResult(descMap.value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
result << parseAxis(descMap.value)
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
// The size is checked to ensure the attribute report contains X, Y and Z values
// If all three axis are not included then the attribute report is ignored
result << parseAxis(descMap.value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
@@ -229,43 +231,43 @@ private Map parseIasMessage(String description) {
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
break
case '0x0021': // Open/Motion/Wet
case '0x0021': // Open/Motion/Wet
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
break
case '0x0022': // Tamper Alarm
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
case '0x0024': // Supervision Report
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
break
case '0x0025': // Restore Report
case '0x0025': // Restore Report
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
break
case '0x0026': // Trouble/Failure
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0028': // Test Mode
break
}
return resultMap
}
def updated() {
@@ -300,163 +302,171 @@ def getTemperature(value) {
}
}
private Map getBatteryResult(rawValue) {
log.debug "Battery"
log.debug rawValue
def linkText = getLinkText(device)
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
def result = [
name: 'battery',
value: '--'
]
value: '--'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 255) {}
else {
if (volts > 3.5) {
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}}
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
return result
}
private Map getTemperatureResult(value) {
log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
}
return result
}
private Map getTemperatureResult(value) {
log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getContactResult(value) {
log.debug "Contact"
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
}
private getAccelerationResult(numValue) {
log.debug "Acceleration"
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
isStateChange: isStateChange
]
}
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = []
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
private Map getContactResult(value) {
log.debug "Contact"
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
private getAccelerationResult(numValue) {
log.debug "Acceleration"
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
descriptionText: descriptionText,
isStateChange: isStateChange
]
}
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = []
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
return refreshCmds + enrollResponse()
}
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (D200)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {D200}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse()
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
def configCmds = [
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"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
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //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 30 3600 {6400}",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh()
return configCmds + refresh()
}
private getEndpointId() {
@@ -471,59 +481,43 @@ def enrollResponse() {
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private Map parseAxis(String description) {
log.debug "parseAxis"
def xyzResults = [x: 0, y: 0, z: 0]
def parts = description.split("2900")
parts[0] = "12" + parts[0]
parts.each { part ->
part = part.trim()
if (part.startsWith("12")) {
def unsignedX = hexToInt(part.split("12")[1].trim())
def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX
xyzResults.x = signedX
log.debug "X Part: ${signedX}"
}
// Y and the Z axes are interchanged between SmartThings's implementation and Centralite's implementation
else if (part.startsWith("13")) {
def unsignedY = hexToInt(part.split("13")[1].trim())
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
if (device.getDataValue("manufacturer") == "SmartThings") {
xyzResults.z = -signedY
log.debug "Z Part: ${xyzResults.z}"
if (garageSensor == "Yes")
garageEvent(xyzResults.z)
}
else {
xyzResults.y = signedY
log.debug "Y Part: ${signedY}"
}
}
else if (part.startsWith("14")) {
def unsignedZ = hexToInt(part.split("14")[1].trim())
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
if (device.getDataValue("manufacturer") == "SmartThings") {
xyzResults.y = signedZ
log.debug "Y Part: ${signedZ}"
} else {
xyzResults.z = signedZ
log.debug "Z Part: ${signedZ}"
if (garageSensor == "Yes")
garageEvent(signedZ)
}
}
}
def z = hexToSignedInt(description[0..3])
def y = hexToSignedInt(description[10..13])
def x = hexToSignedInt(description[20..23])
def xyzResults = [x: x, y: y, z: z]
if (device.getDataValue("manufacturer") == "SmartThings") {
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
xyzResults.x = z
xyzResults.y = y
xyzResults.z = -x
} else {
// The axises reported by the Device Handler differ from the axises reported by the sensor
// This may change in the future
xyzResults.x = z
xyzResults.y = x
xyzResults.z = y
}
log.debug "parseAxis -- ${xyzResults}"
if (garageSensor == "Yes")
garageEvent(xyzResults.z)
getXyzResult(xyzResults, description)
}
private hexToSignedInt(hexVal) {
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def garageEvent(zValue) {
def absValue = zValue.abs()
def contactValue = null
@@ -596,5 +590,3 @@ private byte[] reverseArray(byte[] array) {
}
return array
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 SmartThings
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -15,6 +15,7 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
command "tempUp"
command "tempDown"
@@ -22,11 +23,40 @@ metadata {
command "heatDown"
command "coolUp"
command "coolDown"
command "setTemperature", ["number"]
command "setTemperature", ["number"]
}
tiles {
valueTile("temperature", "device.temperature", width: 1, height: 1) {
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}', unit:"dF",
backgroundColors:[
[value: 31, color: "#153591"],
@@ -39,51 +69,51 @@ metadata {
]
)
}
standardTile("tempDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"tempDown"
}
standardTile("tempUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"tempUp"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
}
standardTile("heatDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"heatDown"
}
standardTile("heatUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"heatUp"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
}
standardTile("coolDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"coolDown"
}
standardTile("coolUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"coolUp"
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
}
standardTile("operatingState", "device.thermostatOperatingState") {
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
state "idle", label:'${name}', backgroundColor:"#ffffff"
state "heating", label:'${name}', backgroundColor:"#ffa81e"
state "cooling", label:'${name}', backgroundColor:"#269bd2"
}
main("temperature","operatingState")
main("thermostatMulti")
details([
"temperature","tempDown","tempUp",
"mode", "fanMode", "operatingState",
@@ -101,6 +131,7 @@ def installed() {
sendEvent(name: "thermostatMode", value: "off")
sendEvent(name: "thermostatFanMode", value: "fanAuto")
sendEvent(name: "thermostatOperatingState", value: "idle")
sendEvent(name: "humidity", value: 53, unit: "%")
}
def parse(String description) {

View File

@@ -29,7 +29,7 @@ metadata {
tiles {
valueTile("power", "device.power") {
valueTile("power", "device.power", canChangeIcon: true) {
state "power", label: '${currentValue} W'
}

View File

@@ -273,7 +273,7 @@ User-Agent: CyberGarage-HTTP/1.0
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
runIn(30, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -77,9 +77,8 @@ def parse(String description) {
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
@@ -134,7 +133,7 @@ def refresh() {
def getStatus() {
log.debug "Executing WeMo Motion 'getStatus'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
runIn(30, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -28,6 +28,7 @@
command "subscribe"
command "resubscribe"
command "unsubscribe"
command "setOffline"
}
// simulator metadata
@@ -207,7 +208,7 @@ def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
@@ -275,7 +276,7 @@ def setOffline() {
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
runIn(30, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
@@ -290,4 +291,4 @@ User-Agent: CyberGarage-HTTP/1.0
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Author: SmartThings
* Date: 2016-01-19
*
* This DTH should serve as the generic DTH to handle RGBW ZigBee HA devices
*/
metadata {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Color Temperature"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getEvent(description)
if (finalResult) {
log.debug finalResult
sendEvent(finalResult)
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap"
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
}
}
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
def refresh() {
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + 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)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
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)
}
def setColorTemperature(value) {
zigbee.setColorTemperature(value)
}
def setLevel(value) {
zigbee.setLevel(value)
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + "delay 300" + setSaturation(value.saturation)
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
}

View File

@@ -275,6 +275,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
case 32:
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
allCodesDeleted()
break
case 33:
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
@@ -341,14 +342,14 @@ def zwaveEvent(UserCodeReport cmd) {
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
map.isStateChange = (code != decrypt(state[name]))
map.isStateChange = true
}
result << createEvent(map)
} else {
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
map.displayed = map.isStateChange = false
map.displayed = map.isStateChange = true
result << createEvent(map)
state["set$name"] = state["reset$name"]
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
@@ -360,7 +361,7 @@ def zwaveEvent(UserCodeReport cmd) {
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
}
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
map.isStateChange = state[name] as Boolean
map.isStateChange = true
result << createEvent(map)
}
code = ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,189 @@
/**
* Vinli Home Beta
*
* Copyright 2015 Daniel
*
*/
definition(
name: "Vinli Home Connect",
namespace: "com.vinli.smartthings",
author: "Daniel",
description: "Allows Vinli users to connect their car to SmartThings",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_60.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
oauth: true)
preferences {
section ("Allow external service to control these things...") {
input "switches", "capability.switch", multiple: true, required: true
input "locks", "capability.lock", multiple: true, required: true
}
}
mappings {
path("/devices") {
action: [
GET: "listAllDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitches"
]
}
path("/switches/:id/:command") {
action: [
PUT: "updateSwitch"
]
}
path("/locks/:command") {
action: [
PUT: "updateLocks"
]
}
path("/locks/:id/:command") {
action: [
PUT: "updateLock"
]
}
path("/devices/:id/:command") {
action: [
PUT: "commandDevice"
]
}
}
// returns a list of all devices
def listAllDevices() {
def resp = []
switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
}
locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
}
return resp
}
// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {
def resp = []
switches.each {
resp << [name: it.displayName, value: it.currentValue("switch"), type: "switch", id: it.id]
}
return resp
}
void updateLocks() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
locks.each {
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
locks."$command"()
}
}
void updateLock() {
def command = params.command
locks.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all lock specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void updateSwitch() {
def command = params.command
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void commandDevice() {
def command = params.command
def devices = []
switches.each {
devices << it
}
locks.each {
devices << it
}
devices.each {
if (it.id == params.id) {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for specified device")
}
it."$command"()
}
}
}
void updateSwitches() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
switches."$command"()
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
}

View File

@@ -1,7 +1,7 @@
/**
* Initial State Event Streamer
*
* Copyright 2015 David Sulpy
* Copyright 2016 David Sulpy
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -77,6 +77,62 @@ mappings {
}
}
def getAccessKey() {
log.trace "get access key"
if (atomicState.accessKey == null) {
httpError(404, "Access Key Not Found")
} else {
[
accessKey: atomicState.accessKey
]
}
}
def getBucketKey() {
log.trace "get bucket key"
if (atomicState.bucketKey == null) {
httpError(404, "Bucket key Not Found")
} else {
[
bucketKey: atomicState.bucketKey,
bucketName: atomicState.bucketName
]
}
}
def setBucketKey() {
log.trace "set bucket key"
def newBucketKey = request.JSON?.bucketKey
def newBucketName = request.JSON?.bucketName
log.debug "bucket name: $newBucketName"
log.debug "bucket key: $newBucketKey"
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
atomicState.bucketKey = "$newBucketKey"
atomicState.bucketName = "$newBucketName"
atomicState.isBucketCreated = false
}
tryCreateBucket()
}
def setAccessKey() {
log.trace "set access key"
def newAccessKey = request.JSON?.accessKey
def newGrokerSubdomain = request.JSON?.grokerSubdomain
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
atomicState.grokerSubdomain = "$newGrokerSubdomain"
atomicState.isBucketCreated = false
}
if (newAccessKey && newAccessKey != atomicState.accessKey) {
atomicState.accessKey = "$newAccessKey"
atomicState.isBucketCreated = false
}
}
def subscribeToEvents() {
if (accelerometers != null) {
subscribe(accelerometers, "acceleration", genericHandler)
@@ -169,85 +225,27 @@ def subscribeToEvents() {
}
}
def getAccessKey() {
log.trace "get access key"
if (atomicState.accessKey == null) {
httpError(404, "Access Key Not Found")
} else {
[
accessKey: atomicState.accessKey
]
}
}
def getBucketKey() {
log.trace "get bucket key"
if (atomicState.bucketKey == null) {
httpError(404, "Bucket key Not Found")
} else {
[
bucketKey: atomicState.bucketKey,
bucketName: atomicState.bucketName
]
}
}
def setBucketKey() {
log.trace "set bucket key"
def newBucketKey = request.JSON?.bucketKey
def newBucketName = request.JSON?.bucketName
log.debug "bucket name: $newBucketName"
log.debug "bucket key: $newBucketKey"
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
atomicState.bucketKey = "$newBucketKey"
atomicState.bucketName = "$newBucketName"
atomicState.isBucketCreated = false
}
tryCreateBucket()
}
def setAccessKey() {
log.trace "set access key"
def newAccessKey = request.JSON?.accessKey
def newGrokerSubdomain = request.JSON?.grokerSubdomain
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
atomicState.grokerSubdomain = "$newGrokerSubdomain"
atomicState.isBucketCreated = false
}
if (newAccessKey && newAccessKey != atomicState.accessKey) {
atomicState.accessKey = "$newAccessKey"
atomicState.isBucketCreated = false
}
}
def installed() {
atomicState.version = "1.0.18"
atomicState.version = "1.1.0"
atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker"
subscribeToEvents()
atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker"
atomicState.eventBuffer = []
runEvery15Minutes(flushBuffer)
log.debug "installed (version $atomicState.version)"
}
def updated() {
atomicState.version = "1.0.18"
atomicState.version = "1.1.0"
unsubscribe()
if (atomicState.bucketKey != null && atomicState.accessKey != null) {
atomicState.isBucketCreated = false
}
if (atomicState.eventBuffer == null) {
atomicState.eventBuffer = []
}
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
atomicState.grokerSubdomain = "groker"
}
@@ -327,37 +325,17 @@ def genericHandler(evt) {
eventHandler(key, value)
}
// This is a handler function for flushing the event buffer
// after a specified amount of time to reduce the load on ST servers
def flushBuffer() {
def eventBuffer = atomicState.eventBuffer
log.trace "About to flush the buffer on schedule"
if (eventBuffer != null && eventBuffer.size() > 0) {
atomicState.eventBuffer = []
tryShipEvents(eventBuffer)
}
}
def eventHandler(name, value) {
def epoch = now() / 1000
def eventBuffer = atomicState.eventBuffer ?: []
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
if (eventBuffer.size() >= 10) {
// Clear eventBuffer right away since we've already pulled it off of atomicState to reduce the risk of missing
// events. This assumes the grokerSubdomain, accessKey, and bucketKey are set correctly to avoid the eventBuffer
// from growing unbounded.
atomicState.eventBuffer = []
tryShipEvents(eventBuffer)
} else {
// Make sure we persist the updated eventBuffer with the new event added back to atomicState
atomicState.eventBuffer = eventBuffer
}
log.debug "Event added to buffer: " + eventBuffer
def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
tryShipEvents(event)
log.debug "Shipped Event: " + event
}
// a helper function for shipping the atomicState.eventBuffer to Initial State
def tryShipEvents(eventBuffer) {
def tryShipEvents(event) {
def grokerSubdomain = atomicState.grokerSubdomain
// can't ship events if there is no grokerSubdomain
@@ -380,7 +358,7 @@ def tryShipEvents(eventBuffer) {
"X-IS-AccessKey": "${accessKey}",
"Accept-Version": "0.0.2"
],
body: eventBuffer
body: event
]
try {

View File

@@ -13,7 +13,7 @@ preferences{
input "lock1", "capability.lock", required: true
}
section("Select the door contact sensor:") {
input "contact", "capability.contactSensor", required: true
input "contact", "capability.contactSensor", required: true
}
section("Automatically lock the door when closed...") {
input "minutesLater", "number", title: "Delay (in minutes):", required: true
@@ -22,9 +22,10 @@ preferences{
input "secondsLater", "number", title: "Delay (in seconds):", required: true
}
section( "Notifications" ) {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
input "phoneNumber", "phone", title: "Enter phone number to send text notification.", required: false
}
input("recipients", "contact", title: "Send notifications to", required: false) {
input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false
}
}
}
def installed(){
@@ -42,55 +43,73 @@ def initialize(){
subscribe(lock1, "lock", doorHandler, [filterEvents: false])
subscribe(lock1, "unlock", doorHandler, [filterEvents: false])
subscribe(contact, "contact.open", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
}
def lockDoor(){
log.debug "Locking the door."
lock1.lock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
}
}
if (phoneNumber) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
}
}
def unlockDoor(){
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
}
}
def doorHandler(evt){
if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...
def delay = (secondsLater) // runIn uses seconds
runIn( delay, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
//def delay = (secondsLater) // runIn uses seconds
runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
}
else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
unschedule( unlockDoor ) // ...we don't need to unlock it later.
}
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
else { //Opening or Closing door when locked (in case you have a handle lock)
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
}
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 SmartThings
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -38,37 +38,75 @@ preferences {
page(name: "schedulingPage")
page(name: "completionPage")
page(name: "numbersPage")
page(name: "controllerExplanationPage")
}
def rootPage() {
dynamicPage(name: "rootPage", title: "", install: true, uninstall: true) {
section {
section("What to dim") {
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
if (dimmers) {
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
}
}
if (dimmers) {
section {
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
section("Gentle Wake Up Has A Controller") {
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
}
section {
href(name: "toSchedulingPage", page: "schedulingPage", title: "Rules For Automatically Dimming Your Lights", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "")
}
section {
href(name: "toCompletionPage", title: "Completion Actions (Optional)", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription())
section("Rules For Dimming") {
href(name: "toSchedulingPage", page: "schedulingPage", title: "Automation", description: schedulingHrefDescription() ?: "Set rules for when to start", state: schedulingHrefDescription() ? "complete" : "")
input(name: "manualOverride", type: "enum", options: ["cancel": "Cancel dimming", "jumpTo": "Jump to the end"], title: "When one of the dimmers is manually turned off…", description: "dimming will continue", required: false, multiple: false)
href(name: "toCompletionPage", title: "Completion Actions", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription() ?: "Set rules for what to do when dimming completes")
}
section {
// TODO: fancy label
label(title: "Label this SmartApp", required: false, defaultValue: "")
label(title: "Label This SmartApp", required: false, defaultValue: "", description: "Highly recommended", submitOnChange: true)
}
}
}
}
def controllerExplanationPage() {
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
section("With other SmartApps", hideable: true, hidden: false) {
paragraph "When this SmartApp is installed, it will create a controller device which you can use in other SmartApps for even more customizable automation!"
paragraph "The controller acts like a switch so any SmartApp that can control a switch can control Gentle Wake Up, too!"
paragraph "Routines and 'Smart Lighting' are great ways to automate Gentle Wake Up."
}
section("More about the controller", hideable: true, hidden: true) {
paragraph "You can find the controller with your other 'Things'. It will look like this."
image "http://f.cl.ly/items/2O0v0h41301U14042z3i/GentleWakeUpController-tile-stopped.png"
paragraph "You can start and stop Gentle Wake up by tapping the control on the right."
image "http://f.cl.ly/items/3W323J3M1b3K0k0V3X3a/GentleWakeUpController-tile-running.png"
paragraph "If you look at the device details screen, you will find even more information about Gentle Wake Up and more fine grain controls."
image "http://f.cl.ly/items/291s3z2I2Q0r2q0x171H/GentleWakeUpController-richTile-stopped.png"
paragraph "The slider allows you to jump to any point in the dimming process. Think of it as a percentage. If Gentle Wake Up is set to dim down as you fall asleep, but your book is just too good to put down; simply drag the slider to the left and Gentle Wake Up will give you more time to finish your chapter and drift off to sleep."
image "http://f.cl.ly/items/0F0N2G0S3v1q0L0R3J3Y/GentleWakeUpController-richTile-running.png"
paragraph "In the lower left, you will see the amount of time remaining in the dimming cycle. It does not count down evenly. Instead, it will update whenever the slider is updated; typically every 6-18 seconds depending on the duration of your dimming cycle."
paragraph "Of course, you may also tap the middle to start or stop the dimming cycle at any time."
}
section("Starting and stopping the SmartApp itself", hideable: true, hidden: true) {
paragraph "Tap the 'play' button on the SmartApp to start or stop dimming."
image "http://f.cl.ly/items/0R2u1Z2H30393z2I2V3S/GentleWakeUp-appTouch2.png"
}
section("Turning off devices while dimming", hideable: true, hidden: true) {
paragraph "It's best to use other Devices and SmartApps for triggering the Controller device. However, that isn't always an option."
paragraph "If you turn off a switch that is being dimmed, it will either continue to dim, stop dimming, or jump to the end of the dimming cycle depending on your settings."
paragraph "Unfortunately, some switches take a little time to turn off and may not finish turning off before Gentle Wake Up sets its dim level again. You may need to try a few times to get it to stop."
paragraph "That's why it's best to use devices that aren't currently dimming. Remember that you can use other SmartApps to toggle the controller. :)"
}
}
}
def numbersPage() {
dynamicPage(name:"numbersPage", title:"") {
@@ -128,24 +166,33 @@ def endLevelLabel() {
return "${endLevel}%"
}
def weekdays() {
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
def weekends() {
["Saturday", "Sunday"]
}
def schedulingPage() {
dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") {
section {
input(name: "days", type: "enum", title: "Allow Automatic Dimming On These Days", description: "Every day", required: false, multiple: true, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
section("Use Other SmartApps!") {
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
}
section {
input(name: "modeStart", title: "Start when entering this mode", type: "mode", required: false, mutliple: false, submitOnChange: true)
section("Allow Automatic Dimming") {
input(name: "days", type: "enum", title: "On These Days", description: "Every day", required: false, multiple: true, options: weekdays() + weekends())
}
section("Start Dimming...") {
input(name: "startTime", type: "time", title: "At This Time", description: null, required: false)
input(name: "modeStart", title: "When Entering This Mode", type: "mode", required: false, mutliple: false, submitOnChange: true, description: null)
if (modeStart) {
input(name: "modeStop", title: "Stop when leaving '${modeStart}' mode", type: "bool", required: false)
}
}
section {
input(name: "startTime", type: "time", title: "Start Dimming At This Time", description: null, required: false)
}
}
}
@@ -194,11 +241,16 @@ def updated() {
log.debug "Updating 'Gentle Wake Up' with settings: ${settings}"
unschedule()
def controller = getController()
if (controller) {
controller.label = app.label
}
initialize()
}
private initialize() {
stop()
stop("settingsChange")
if (startTime) {
log.debug "scheduling dimming routine to run at $startTime"
@@ -209,15 +261,27 @@ private initialize() {
subscribe(app, appHandler)
subscribe(location, locationHandler)
if (manualOverride) {
subscribe(dimmers, "switch.off", stopDimmersHandler)
}
if (!getAllChildDevices()) {
// create controller device and set name to the label used here
def dni = "${new Date().getTime()}"
log.debug "app.label: ${app.label}"
addChildDevice("smartthings", "Gentle Wake Up Controller", dni, null, ["label": app.label])
state.controllerDni = dni
}
}
def appHandler(evt) {
log.debug "appHandler evt: ${evt.value}"
if (evt.value == "touch") {
if (atomicState.running) {
stop()
stop("appTouch")
} else {
start()
start("appTouch")
}
}
}
@@ -233,26 +297,47 @@ def locationHandler(evt) {
def modeStopIsTrue = (modeStop && modeStop != "false")
if (isSpecifiedMode && canStartAutomatically()) {
start()
start("modeChange")
} else if (!isSpecifiedMode && modeStopIsTrue) {
stop()
stop("modeChange")
}
}
def stopDimmersHandler(evt) {
log.trace "stopDimmersHandler evt: ${evt.value}"
def percentComplete = completionPercentage()
// Often times, the first thing we do is turn lights on or off so make sure we don't stop as soon as we start
if (percentComplete > 2 && percentComplete < 98) {
if (manualOverride == "cancel") {
log.debug "STOPPING in stopDimmersHandler"
stop("manualOverride")
} else if (manualOverride == "jumpTo") {
def end = dynamicEndLevel()
log.debug "Jumping to 99% complete in stopDimmersHandler"
jumpTo(99)
}
} else {
log.debug "not stopping in stopDimmersHandler"
}
}
// ========================================================
// Scheduling
// ========================================================
def scheduledStart() {
if (canStartAutomatically()) {
start()
start("schedule")
}
}
def start() {
public def start(source) {
log.trace "START"
sendStartEvent(source)
setLevelsInState()
atomicState.running = true
@@ -263,9 +348,11 @@ def start() {
increment()
}
def stop() {
public def stop(source) {
log.trace "STOP"
sendStopEvent(source)
atomicState.running = false
atomicState.start = 0
@@ -282,6 +369,110 @@ private healthCheck() {
increment()
}
// ========================================================
// Controller
// ========================================================
def sendStartEvent(source) {
log.trace "sendStartEvent(${source})"
def eventData = [
name: "sessionStatus",
value: "running",
descriptionText: "${app.label} has started dimming",
displayed: true,
linkText: app.label,
isStateChange: true
]
if (source == "modeChange") {
eventData.descriptionText += " because of a mode change"
} else if (source == "schedule") {
eventData.descriptionText += " as scheduled"
} else if (source == "appTouch") {
eventData.descriptionText += " because you pressed play on the app"
} else if (source == "controller") {
eventData.descriptionText += " because you pressed play on the controller"
}
sendControllerEvent(eventData)
}
def sendStopEvent(source) {
log.trace "sendStopEvent(${source})"
def eventData = [
name: "sessionStatus",
value: "stopped",
descriptionText: "${app.label} has stopped dimming",
displayed: true,
linkText: app.label,
isStateChange: true
]
if (source == "modeChange") {
eventData.descriptionText += " because of a mode change"
eventData.value += "cancelled"
} else if (source == "schedule") {
eventData.descriptionText = "${app.label} has finished dimming"
} else if (source == "appTouch") {
eventData.descriptionText += " because you pressed play on the app"
eventData.value += "cancelled"
} else if (source == "controller") {
eventData.descriptionText += " because you pressed stop on the controller"
eventData.value += "cancelled"
} else if (source == "settingsChange") {
eventData.descriptionText += " because the settings have changed"
eventData.value += "cancelled"
} else if (source == "manualOverride") {
eventData.descriptionText += " because the dimmer was manually turned off"
eventData.value += "cancelled"
}
sendControllerEvent(eventData)
sendTimeRemainingEvent(0)
}
def sendTimeRemainingEvent(percentComplete) {
log.trace "sendTimeRemainingEvent(${percentComplete})"
def percentCompleteEventData = [
name: "percentComplete",
value: percentComplete as int,
displayed: true,
isStateChange: true
]
sendControllerEvent(percentCompleteEventData)
def duration = sanitizeInt(duration, 30)
def timeRemaining = duration - (duration * (percentComplete / 100))
def timeRemainingEventData = [
name: "timeRemaining",
value: displayableTime(timeRemaining),
displayed: true,
isStateChange: true
]
sendControllerEvent(timeRemainingEventData)
}
def sendControllerEvent(eventData) {
def controller = getController()
if (controller) {
controller.controllerEvent(eventData)
}
}
def getController() {
def dni = state.controllerDni
if (!dni) {
log.warn "no controller dni"
return null
}
def controller = getChildDevice(dni)
if (!controller) {
log.warn "no controller"
return null
}
log.debug "controller: ${controller}"
return controller
}
// ========================================================
// Setting levels
// ========================================================
@@ -349,6 +540,8 @@ def updateDimmers(percentComplete) {
}
}
sendTimeRemainingEvent(percentComplete)
}
int dynamicLevel(dimmer, percentComplete) {
@@ -377,7 +570,7 @@ private completion() {
return
}
stop()
stop("schedule")
handleCompletionSwitches()
@@ -385,6 +578,7 @@ private completion() {
handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
}
private handleCompletionSwitches() {
@@ -493,22 +687,65 @@ def completionPercentage() {
return
}
int now = new Date().getTime()
int diff = now - atomicState.start
int totalRunTime = totalRunTimeMillis()
int percentOfRunTime = (diff / totalRunTime) * 100
log.debug "percentOfRunTime: ${percentOfRunTime}"
def now = new Date().getTime()
def timeElapsed = now - atomicState.start
def totalRunTime = totalRunTimeMillis()
def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentComplete: ${percentComplete}"
percentOfRunTime
return percentComplete
}
int totalRunTimeMillis() {
int minutes = sanitizeInt(duration, 30)
convertToMillis(minutes)
}
int convertToMillis(minutes) {
def seconds = minutes * 60
def millis = seconds * 1000
return millis as int
return millis
}
def timeRemaining(percentComplete) {
def normalizedPercentComplete = percentComplete / 100
def duration = sanitizeInt(duration, 30)
def timeElapsed = duration * normalizedPercentComplete
def timeRemaining = duration - timeElapsed
return timeRemaining
}
int millisToEnd(percentComplete) {
convertToMillis(timeRemaining(percentComplete))
}
String displayableTime(timeRemaining) {
def timeString = "${timeRemaining}"
def parts = timeString.split(/\./)
if (!parts.size()) {
return "0:00"
}
def minutes = parts[0]
if (parts.size() == 1) {
return "${minutes}:00"
}
def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padRight(2, "0")
return "${minutes}:${seconds}"
}
def jumpTo(percentComplete) {
def millisToEnd = millisToEnd(percentComplete)
def endTime = new Date().getTime() + millisToEnd
def duration = sanitizeInt(duration, 30)
def durationMillis = convertToMillis(duration)
def shiftedStart = endTime - durationMillis
atomicState.start = shiftedStart
updateDimmers(percentComplete)
sendTimeRemainingEvent(percentComplete)
}
int dynamicEndLevel() {
if (usesOldSettings()) {
if (direction && direction == "Down") {
@@ -673,7 +910,13 @@ def schedulingHrefDescription() {
def descriptionParts = []
if (days) {
descriptionParts << "On ${fancyString(days)},"
if (days == weekdays()) {
descriptionParts << "On weekdays,"
} else if (days == weekends()) {
descriptionParts << "On weekends,"
} else {
descriptionParts << "On ${fancyString(days)},"
}
}
descriptionParts << "${fancyDeviceString(dimmers)} will start dimming"
@@ -759,15 +1002,15 @@ def completionHrefDescription() {
def numbersPageHrefDescription() {
def title = "All dimmers will dim for ${duration ?: '30'} minutes from ${startLevelLabel()} to ${endLevelLabel()}"
if (colorize) {
def colorDimmers = dimmersWithSetColorCommand()
if (colorDimmers == dimmers) {
title += " and will gradually change color."
} else {
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
}
}
return title
if (colorize) {
def colorDimmers = dimmersWithSetColorCommand()
if (colorDimmers == dimmers) {
title += " and will gradually change color."
} else {
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
}
}
return title
}
def hueSatToHex(h, s) {

View File

@@ -258,8 +258,8 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unschedule()
unsubscribe()
unschedule()
initialize()
}
@@ -325,6 +325,8 @@ def addBulbs() {
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
@@ -332,10 +334,8 @@ def addBulbs() {
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
d.refresh()
}
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) {

View File

@@ -316,60 +316,40 @@ private def parseEventMessage(String description) {
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.mac = valueString
}
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ip = valueString
}
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.port = valueString
}
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ssdpPath = valueString
}
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString) {
event.ssdpUSN = valueString
}
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString) {
event.ssdpTerm = valueString
}
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
def valueString = part.trim()
if (valueString) {
event.headers = valueString
}
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
def valueString = part.trim()
if (valueString) {
event.body = valueString
}
event.body = part.trim()
}
}
event

View File

@@ -473,68 +473,48 @@ private def parseXmlBody(def body) {
}
private def parseDiscoveryMessage(String description) {
def device = [:]
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
def valueString = part.split(":")[1].trim()
device.devicetype = valueString
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.mac = valueString
}
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ip = valueString
}
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.port = valueString
}
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ssdpPath = valueString
}
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString) {
device.ssdpUSN = valueString
}
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString) {
device.ssdpTerm = valueString
}
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
def valueString = part.trim()
if (valueString) {
device.headers = valueString
}
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
def valueString = part.trim()
if (valueString) {
device.body = valueString
}
event.body = part.trim()
}
}
device
event
}
def doDeviceSync(){