Compare commits

..

175 Commits

Author SHA1 Message Date
Tim Slagle
4a14421ef0 test: test 2017-02-01 14:46:54 -08:00
Vinay Rao
de1894bfbf Merge pull request #1630 from SmartThingsCommunity/staging
Rolling down staging to master
2017-01-31 13:47:45 -08:00
Jason Botello
d0f8ec87bd Merge pull request #1616 from jasonbio/plantlink-exceptions
Plant link Exceptions
2017-01-30 15:01:18 -08:00
Jason Botello
1383ab1e07 Merge pull request #1615 from jasonbio/netatmo-exceptions
Netatmo Exceptions
2017-01-30 15:01:05 -08:00
Vinay Rao
d4f21b95d7 Merge pull request #1621 from varzac/zll-bulb-reporting
[DPROT-227] Manually refresh on/off status after setLevel
2017-01-27 16:37:34 -08:00
Zach Varberg
be2e19e406 Manually refresh on/off status after setLevel
This is because ZLL bulbs do not report or allow for configuration of
reporting of attributes.  As a result if we change a value, we have to
manually call a read of that attribute to update our status.

This is a part of: https://smartthings.atlassian.net/browse/DPROT-227
2017-01-26 11:27:51 -06:00
Vinay Rao
54da556c17 Merge pull request #1620 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2017-01-25 15:48:58 -08:00
Vinay Rao
8611d2e2d2 Merge pull request #1618 from workingmonk/feature/light_capability
ICP-203 Adding Light Capability to hue lights
2017-01-25 04:13:47 -08:00
Vinay Rao
c650047f31 ICP-203 Adding Light Capability to hue lights 2017-01-25 04:12:42 -08:00
Jason Botello
41adc9777a Plant link Exceptions
Adding try/catch blocks to reduce HTTP exceptions due to rate limits or
unauthorized errors.
2017-01-24 14:53:23 -08:00
Jason Botello
f334f6505a Netatmo Exceptions
Adding try/catch blocks to reduce HTTP exceptions due to rate limits or
unauthorized errors. Also updating the “devicelist” endpoint to
“getstationdata” as the former is deprecated
2017-01-24 14:30:59 -08:00
Vinay Rao
ded7501b84 Merge pull request #1614 from SmartThingsCommunity/master
Rolling up master to staging
2017-01-24 13:21:44 -08:00
Vinay Rao
eb4d5dcfb8 Merge pull request #1613 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2017-01-24 13:19:33 -08:00
Vinay Rao
6385443f20 Merge pull request #1612 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2017-01-24 12:48:34 -08:00
Vinay Rao
fd1ad51880 Merge branch 'staging' into production 2017-01-24 12:48:21 -08:00
Vinay Rao
550214ceb5 Merge pull request #1610 from dkirker/DEVC-490
DEVC-490: Add Leviton Dimmer DL1KD fingerprint
2017-01-23 11:53:01 -08:00
Donald Kirker
a36500a216 DEVC-490: Add Leviton Dimmer DL1KD fingerprint 2017-01-23 11:45:05 -08:00
Juan Pablo Risso
445c115c14 DVCSMP-2324 - Centralite Thermostat handle multiple attributes (#1606)
- Curly braces
- remove parseDescriptionAsMap
- descMap to it
- Appended to the result list
- Convert to list and appended
2017-01-20 17:49:15 -05:00
bflorian
9900e532a4 Merge pull request #1608 from juano2310/sim_fix
DVCSMP-2325 - Remove parse method from simulated Switch
2017-01-20 14:27:08 -08:00
juano2310
7648fd4a17 DVCSMP-2325 - Remove parse method from simulated Switch
sin string
2017-01-20 17:22:10 -05:00
Vinay Rao
9686f3770b Merge pull request #1604 from workingmonk/feature/device_lab_cert_work
DEVC-492 DEVC-493 DEVC-494 DEVC-526 Device Certification approved new fingerprints
2017-01-19 15:02:07 -08:00
Vinay Rao
de37d0c813 Device Certification approved new fingerprints 2017-01-19 14:38:59 -08:00
Vinay Rao
bd3367fe0e Merge pull request #1598 from SmartThingsCommunity/staging
Rolling down staging to master
2017-01-18 15:18:08 -08:00
Vinay Rao
30511d74af Merge pull request #1597 from SmartThingsCommunity/staging
Rolling up staging to production
2017-01-18 13:27:50 -08:00
Vinay Rao
f0f02a2c00 Merge pull request #1596 from SmartThingsCommunity/production
Rolling down production to staging
2017-01-18 13:26:38 -08:00
Vinay Rao
57d20e2fca Merge pull request #1575 from larsfinander/DVCSMP-2298_Philips_Hue_Reduce_daily_exceptions_staging
DVCSMP-2298 Philips Hue: Reduce number of daily exceptions
2017-01-12 11:14:01 -08:00
Vinay Rao
3216f63cc0 Merge pull request #1581 from larsfinander/MSA-1577_OpenT2T_staging
MSA-1577: OpenT2T
2017-01-11 15:59:25 -08:00
Vinay Rao
7cbc2d1780 Merge pull request #1585 from SmartThingsCommunity/master
Rolling up master to staging
2017-01-10 16:25:33 -08:00
Vinay Rao
5ad20fbd2a Merge pull request #1584 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2017-01-10 16:08:36 -08:00
Vinay Rao
a17971d68c Merge pull request #1583 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2017-01-10 15:01:44 -08:00
Vinay Rao
076ffecd19 Merge pull request #1582 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2017-01-10 15:00:27 -08:00
Duncan McKee
52357e4c50 Merge pull request #1580 from SmartThingsCommunity/DEVC-533
Add fingerprint for GE Smart Motion Sensor DEVC-533
2017-01-10 16:40:43 -05:00
Lars Finander
03a7991279 MSA-1577: OpenT2T
This SmartApp enables the end-to-end SmartThings scenarios
via OpenT2T. More information on OpenT2T can be found at
http://www.opentranslatorstothings.org/#/
2017-01-10 12:32:49 -07:00
Duncan McKee
f969027191 Add fingerprint for GE Smart Motion Sensor DEVC-533 2017-01-10 12:55:13 -05:00
Vinay Rao
8db0556696 Merge pull request #1562 from varzac/increase-delays
DPROT-223 Increase zigbee message delays
2017-01-10 09:35:46 -08:00
Juan Pablo Risso
5607a3e346 DVCSMP-2309 - Wemo Exceptions (#1579) 2017-01-10 12:29:53 -05:00
Lars Finander
bd44027038 DVCSMP-2298 Philips Hue: Reduce number of daily exceptions
-Stop retry when broken data received
-Time out on light discovery if nothing is found
-Avoid div/0 in color temp logic
2017-01-09 15:32:01 -07:00
Vinay Rao
490ec329cb Merge pull request #1570 from workingmonk/feature/iris_button
DEVC-520 DEVC-525 Adding Iris Button after clearing certification
2017-01-06 09:34:49 -08:00
Vinay Rao
3109049122 Merge pull request #1569 from workingmonk/feature/certification_work
DEVC-518 Iris Smart Water Sensor
2017-01-06 09:34:25 -08:00
Zach Varberg
930c4ed914 Increase zigbee message delays
This changes our smartsense DTHs that don't use the ZigBee library for
everything to have larger delays between ZigBee messages.  This is to
reduce the network load to try to work around some of the poorer
behaving ZigBee routers we support.

This resolves: https://smartthings.atlassian.net/browse/DPROT-223
2017-01-06 10:45:13 -06:00
Vinay Rao
259516f21f DEVC-520 DEVC-525 Adding Iris Button after clearing certification 2017-01-05 16:54:46 -08:00
Vinay Rao
b7a08a88e0 DEVC-518 Iris Smart Water Sensor 2017-01-05 16:41:50 -08:00
Jack Chi
c028515fcd Merge pull request #1546 from skt123/dimmer_switch
[CHF-487] Health Check for Z-Wave Dimmer Switch
2016-12-29 10:29:43 -08:00
Jack Chi
751c98d123 Merge pull request #1550 from parijatdas/enerwave
[CHF-488] [CHF-489] [CHF-490] Implementation of HealthCheck for Enerwave Duplex Receptacle ZW15R, Enerwave On/Off Switch ZW15S and Leviton 15A Switch VRS15-1LZ
2016-12-29 10:27:18 -08:00
Jack Chi
5b874e8f3a Merge pull request #1545 from pchomal/dimmerswitch_generic
[CHF-477] Health Check for Z-Wave Dimmer Switch Generic
2016-12-27 11:36:51 -08:00
Parijat Das
9e10405527 Added fingerprints for the following devices:
1. Enerwave Duplex Receptacle ZW15R
2. Enerwave On/Off Switch ZW15S
3. Leviton 15A Switch VRS15-1LZ
2016-12-20 18:33:50 +05:30
sushant.k1
32ceaff54d [CHF-487]
Added Health Check Implementation for:
1. 1,000-Watt In-Wall Smart Dimmer Switch (GE 12725)
2. In-Wall Smart Fan Control (GE 12730)
2016-12-20 12:08:16 +05:30
piyush.c
5b1da30a47 [CHF-477]
Health Check implementation for Z-Wave Dimmer Switch Generic with checkinterval of 32min
2016-12-20 10:39:08 +05:30
bflorian
76e139153a Merge pull request #1532 from tslagle13/fix-color-coordinator-NPE
[DVCSMP-2273] Fix for NPE for slave/master settings
2016-12-15 17:11:01 -05:00
tslagle13
a4bc248006 One further fix for NPEs 2016-12-14 11:40:02 -08:00
tslagle13
e7eb461b4e [DVCSMP-2273] Fix for NPE for slave/master settings 2016-12-13 14:37:49 -08:00
Vinay Rao
0a82077b24 Merge pull request #1519 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-12-07 14:15:16 -08:00
Vinay Rao
083ed7cc9a Merge pull request #1518 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-12-07 12:41:33 -08:00
Vinay Rao
38ef9e5c77 Merge pull request #1517 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-12-07 12:10:01 -08:00
Vinay Rao
6a71615ca5 Merge pull request #1505 from varzac/sendevent-appengine-fix
DPROT-215 Don't use sendEvent for AppEngine parse events
2016-12-02 11:39:24 -08:00
Zach Varberg
9939591005 Don't use sendEvent for AppEngine parse events
With the changes made for
https://smartthings.atlassian.net/browse/DPROT-200 there were a few DTHs
that were using sendEvent to directly send events generated during
parse.  However, because using sendEvent didn't result in parse
returning an event AppEngine would send the description up to the cloud
DTH to be handled.  In some cases this resulted in multiple events for
the same device trigger.

This resolves: https://smartthings.atlassian.net/browse/DPROT-215
2016-12-01 14:52:24 -06:00
Vinay Rao
d7f2bc1d79 Merge pull request #1502 from bflorian/PROB-1426-life360-logging
PROB-1426 adding logging to Life360
2016-11-28 13:20:16 -08:00
bflorian
3c5d727d4c PROB-1426 adding logging to Life360 2016-11-28 16:00:42 -05:00
Vinay Rao
bbad6dfa7a Merge pull request #1501 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-28 10:35:24 -08:00
Vinay Rao
df6c815aa4 Merge pull request #1500 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-11-28 10:34:01 -08:00
Tom Manley
d16ac00eb6 Merge pull request #1493 from tpmanley/hotfix/zll-dimmer-join-failure
zll-dimmer-bulb: Fix bug in configure preventing devices from joining
2016-11-23 23:30:08 -06:00
Tom Manley
1941f56007 zll-dimmer-bulb: Fix bug in configure preventing devices from joining
The configureHealthCheck method and therefore the configure method was
returning a boolean which was causing an exception during the initial
join that would prevent the device from showing up to the user.

https://smartthings.atlassian.net/browse/PROB-1428
2016-11-23 23:01:05 -06:00
Vinay Rao
f34df19a65 Merge pull request #1490 from SmartThingsCommunity/master
Rolling up master to staging
2016-11-22 13:00:00 -08:00
Vinay Rao
af40246655 Merge pull request #1489 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-22 12:59:05 -08:00
Vinay Rao
20239ca982 Merge pull request #1488 from SmartThingsCommunity/staging
Rolling up to production for deploy
2016-11-22 12:43:20 -08:00
Vinay Rao
b20c0e371f Merge pull request #1487 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-11-22 12:42:25 -08:00
Vinay Rao
60756e6dc6 Merge pull request #1478 from yafenzhang/MSA-1591-15
MSA-1591: New Device Handle for Osram Lightify Light(ZHA)
2016-11-21 09:26:08 -08:00
bflorian
ee2e1ee04f Merge pull request #1483 from juano2310/staging
DVCSMP-2211 - Update Button Number
2016-11-18 14:55:18 -08:00
juano2310
e443cb641c DVCSMP-2211 - Update Button Number 2016-11-18 11:28:30 -05:00
Vinay Rao
b9993a9bf2 Merge pull request #1480 from SmartThingsCommunity/master
Rolling up master to staging
2016-11-16 13:38:01 -08:00
Ingvar Marstorp
092971c786 Merge pull request #1334 from marstorp/boseHealth
CHF-420 Add device health check to Bose DTH
2016-11-16 11:06:30 -08:00
Vinay Rao
a8ee927893 Merge pull request #1479 from varzac/fix-motion-sensor
[DPROT-200] Fix broken generation of smartsense-motion events
2016-11-16 08:55:13 -08:00
Zach Varberg
51979f0030 Fix broken generation of smartsense-motion events
When the change went out to allow messages to be passed up to the cloud
the smartsense-motion DTH was broken with the changes for
https://smartthings.atlassian.net/browse/DPROT-200
2016-11-16 10:44:48 -06:00
Zhang Yafen
61d1205e7d MSA-1591: 1.New Device Handle for Osram Lightify Light(ZHA)
(1) SYLVANIA Smart BR30 Softw White
(2) SYLVANIA Smart PAR38 Softw White
(3) SYLVANIA Smart Outdoor RGBW Flex
2. Modify deviceJoinName of NAFTA Lightify product, all the lightify product which use ZHA protocol will be named using "SYLVANIA Smart XXXX".
2016-11-16 19:57:46 +08:00
Vinay Rao
1dfd1818b4 Merge pull request #1471 from SmartThingsCommunity/master
Rolling up master to staging
2016-11-15 14:05:57 -08:00
Vinay Rao
1de73b643c Merge pull request #1470 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-15 14:04:17 -08:00
Vinay Rao
e5c21ef720 Merge pull request #1469 from SmartThingsCommunity/staging
Rolling up staging for production deploy
2016-11-15 13:18:56 -08:00
Vinay Rao
fcb504f57e Merge pull request #1468 from dkirker/sengled_whitetemp
DEVC-523: Add Element Plus bulb
2016-11-15 12:15:27 -08:00
Donald Kirker
b9229c6ef8 Add Element Plus bulb. 2016-11-15 12:05:44 -08:00
Vinay Rao
91a9856a32 Merge pull request #1467 from varzac/revert-eti-binding-table-fixes
[DVCSMP-2175] Revert eti binding table fixes
2016-11-15 11:32:44 -08:00
Zach Varberg
a84ffdde91 Revert "ETI Clear binding table entries to other devices"
This reverts commit 969852602c.
2016-11-15 13:24:23 -06:00
Zach Varberg
3034cc8bcb Revert "Do not delete all binding table entries"
This reverts commit 65bb10d6d6.
2016-11-15 13:23:57 -06:00
Vinay Rao
918e9d9397 Merge pull request #1465 from yafenzhang/MSA-1587-13
MSA-1587: New Device Handle for Osram Lightify Light(ZLL)
2016-11-15 04:09:04 -08:00
Zhang Yafen
0c040120cc MSA-1587: 1.New Device Handle for Osram Lightify Light saled in Europe market(ZLL)
(1) OSRAM LIGHTIFY Gardenpole RGBW
(2) OSRAM LIGHTIFY Outdoor Flex RGBW
(3) OSRAM LIGHTIFY RGBW PAR 16 50
(4) OSRAM LIGHTIFY Flex RGBW, this product uses ZLL, so move it from zigbee-white-color-temperature-bulb to zll-white-color-temperature-bulb
(5) OSRAM LIGHTIFY Classic B40 Tunable White, this product uses ZLL, so move it from zigbee-white-color-temperature-bulb to zll-white-color-temperature-bulb
2016-11-15 18:55:32 +08:00
Vinay Rao
6c84c052cb Merge pull request #1462 from jackchi/health-nov15-hotfix
[CHF-442][DVCSMP-2179] Hotfix for EcoBee thermostat & Osram Tunable White
2016-11-14 15:58:06 -08:00
Vinay Rao
f017bff6ef Merge pull request #1464 from varzac/fix-zigbee-white-tunable-binding-table
[DVCSMP-2175] Do not delete all binding table entries
2016-11-14 14:14:21 -08:00
Zach Varberg
65bb10d6d6 Do not delete all binding table entries
There was a bug when comparing the destination address for binding table
entries that would cause all binding table entries to be deleted.  This
fixes that.

This is a fix for: https://smartthings.atlassian.net/browse/DVCSMP-2175
2016-11-14 14:38:15 -06:00
jackchi
3f93de247b [CHF-442][DVCSMP-2179] Hotfix for EcoBee thermostat & Osram RT5/6 Tunable White naming 2016-11-14 12:05:10 -08:00
Vinay Rao
2b7af3ef8d Merge pull request #1461 from varzac/fix-smartpower-dimming-outlet
[DVCSMP-2227] Handle all messages in smartpower dimming outlet
2016-11-14 11:27:06 -08:00
Zach Varberg
cf9d123aa0 Handle all messages in smartpower dimming outlet
Previously the implementation of isKnownDescription didn't cover all
possible messages that could be parsed and this caused null pointer
exceptions with certain messages.  This now handles all the
possibilities.

This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2227
2016-11-14 11:08:30 -06:00
Vinay Rao
8e37b1fce7 Merge pull request #1460 from bbodenmiller/patch-1
Github -> GitHub (Text change, no ticket)
2016-11-13 05:34:47 -08:00
Ben Bodenmiller
439dd634bd Github -> GitHub 2016-11-12 00:19:55 -08:00
Juan Pablo Risso
1db5f75ec5 DVCSMP-2211 - Update Button Number and Holdable capability (#1458) 2016-11-11 16:10:44 -05:00
Juan Pablo Risso
7978f45996 DVCSMP-2221 - Replace display with displayed (#1457) 2016-11-11 14:09:04 -05:00
Vinay Rao
0ab657d5f2 Merge pull request #1456 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-11 02:47:42 -08:00
Vinay Rao
3d88fc0413 Merge branch 'production' into staging
# Conflicts:
#	devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy
2016-11-11 02:46:41 -08:00
Vinay Rao
ede1296b6b Merge pull request #1447 from workingmonk/delete_dth
CHANGE-751 Deleting Deprecated DTHs that have been migrated to new DTH
2016-11-11 02:35:48 -08:00
Vinay Rao
470cdc7d97 CHANGE-751 Deleting Deprecated DTHs that have been migrated to the new DTH 2016-11-10 14:16:39 -08:00
Vinay Rao
9bc3ff5103 Merge pull request #1443 from jackchi/health-appengine-readme
Device types should include better health documentation
2016-11-09 16:23:00 -08:00
jackchi
bae79192c5 [CHF-459] Health documentation include DTH execution location and expected checkInterval; reduced checkInterval 2016-11-09 13:49:56 -08:00
tslagle13
0ae836b023 Merge pull request #1442 from MichaelStruck/MSA-1576-14
MSA-1576: Color Coordinator
2016-11-09 11:18:22 -08:00
Michael Struck
0b4d555d33 MSA-1576: Fixes various bugs and adds a random color feature. 2016-11-09 09:33:31 -08:00
Vinay Rao
0f8beee455 Merge pull request #1441 from SmartThingsCommunity/master
Rolling up master to staging for next week deploy
2016-11-08 15:24:31 -08:00
Vinay Rao
6ffdc02ef1 Merge pull request #1440 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-08 15:23:48 -08:00
Vinay Rao
6beb8bb50c Merge pull request #1439 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-11-08 14:47:59 -08:00
Tom Manley
83a9df6557 Merge pull request #1438 from tpmanley/bugfix/ge-link-level-events
DVCSMP-2219: GE Link: fixed problem creating level events
2016-11-08 15:18:47 -06:00
Tom Manley
af8590ab01 GE Link: fixed problem creating level events
The bug causing level events not to be created happens when the setLevel
command runs in the cloud but the Device Type Handler is running locally
in the hub. Since state is not synced from the cloud to the hub the
`state.trigger` value was never set to `"setLevel"` on the hub which means
no level events would be created. The change is to not be reliant on state.
Instead we just don't create level events if the level is 0 since there is
no legitimate way for the level to be 0.

https://smartthings.atlassian.net/browse/DVCSMP-2219
2016-11-08 14:48:42 -06:00
Zach Varberg
9599397db8 Merge pull request #1421 from varzac/pass-unhandled-messages-up-to-cloud
DPROT-200: Pass unhandled messages up to the cloud
2016-11-08 09:02:00 -06:00
Jack Chi
f7dbabb6c8 Merge pull request #1430 from skt123/wemo_bulb
[CHF-441] Added Health Check Implementation for WeMo LED Bulb
2016-11-07 22:18:52 -08:00
Jack Chi
7016e234d2 Merge pull request #1431 from skt123/osram_rt5/6_tunable_white
[CHF-442] Added Health Check Implementation for following Osram RT5/6 Tunable White
2016-11-07 22:17:54 -08:00
Jack Chi
cf119b1d15 Merge pull request #1433 from parijatdas/leviton_universal_dimmer
[CHF-455] Implementation of HealthCheck for Leviton Universal Dimmer DZMX1-LZ (Z-wave)
2016-11-07 22:16:05 -08:00
Jack Chi
3e88f3c4bd Merge pull request #1432 from parijatdas/leviton_switch
[CHF-454] [CHF-456] Implementation of HealthCheck for Leviton Switch DZS15-1LZ (Z-wave) and Leviton Outlet DZR15-1LZ (Z-wave)
2016-11-07 22:13:37 -08:00
sushant.k1
8ca20ce87e Added Health Check Implementation for following WeMo LED Bulb 2016-11-08 10:57:49 +05:30
sushant.k1
c1d520a578 Added Health Check Implementation for following Osram RT5/6 Tunable White 2016-11-08 10:53:44 +05:30
Parijat Das
c0bb0554d8 Added fingerprint of Leviton Outlet and Leviton Switch with health check already implemented and modified readme file accordingly 2016-11-08 09:12:48 +05:30
Parijat Das
b6790729c6 Added fingerprint of Leviton Universal Dimmer with health check already implemented 2016-11-08 09:06:36 +05:30
Lars Finander
3675332b75 Merge pull request #1405 from larsfinander/lifx_device_watch_statefix_staging
DVCSMP-2108 LIFX: Add devicewatch support
2016-11-07 09:12:44 -07:00
Vinay Rao
7431346187 Merge pull request #1429 from surfous/DVCSMP-2155_CHF-453-fix
Fix CHF-453 on ZigBee switch power DH
2016-11-04 15:49:51 -07:00
Kevin Shuk
6aa0ff97b3 Fix CHF-453 on ZigBee switch power
* original Health check implementation did not send refresh() commands to hub and thus the device. This fixes that problem.
* updated() does not have its return value processed as a list of hub commands. These must be sent explicitly
* Explicit returns rock
2016-11-04 15:48:28 -07:00
Juan Pablo Risso
bd1ace96de DVCSMP-2178 - Add thermostats to Logitech Harmony (#1408) 2016-11-04 16:41:41 -04:00
Jack Chi
40c4520d08 Merge pull request #1419 from parijatdas/leviton
[CHF-404] [CHF-422] Health check for Leviton DZPA1 Plug-in Appliance Module and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)
2016-11-04 09:24:29 -07:00
Zach Varberg
1f69a42341 Merge pull request #1402 from varzac/fix-binding-table-ETI
[DVCSMP-2175] ETI Clear binding table entries to other devices
2016-11-04 10:45:21 -05:00
Zach Varberg
969852602c ETI Clear binding table entries to other devices
This adds support for ETI devices which have a firmware bug in which the
binding table is not properly cleared on network leave.  So the DTH will
on configure (and refresh) look at the binding table and remove any
entries to not the current hub.

This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2175
2016-11-04 10:02:37 -05:00
Zach Varberg
4115d8c65f Pass unhandled messages up to the cloud
Currently these DTHs return null when they parse a message that they
don't have specific handling for.  This ends up sending the message up
to the cloud as an event, which prevented us from potentially parsing
that message in the cloud.  By instead returning an empty map we can
instead send the message up to the cloud to be parsed there.  This
allows us to add handling in the cloud for new message without requiring
and AppEngine deploy for them to work.

This resolves: https://smartthings.atlassian.net/browse/DPROT-200
2016-11-03 09:59:23 -05:00
Parijat Das
492315b78a Added health check for Leviton DZPA1 Plug-in Appliance Module and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) along with the README.md 2016-11-03 16:10:09 +05:30
Vinay Rao
40acb36009 Merge pull request #1423 from SmartThingsCommunity/staging
Rolling down staging to master
2016-11-02 16:50:04 -07:00
Vinay Rao
0a040aa51b Merge branch 'production' into staging
# Conflicts:
#	devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy
#	devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy
2016-11-02 16:49:03 -07:00
dsainteclaire
8986c4f5d6 Merge pull request #1411 from dsainteclaire/DVCSMP-2179-added-device-watch-to-ecobee
DVCSMP-2179 added device watch for ecobee based on the code from lyric
2016-11-02 14:26:24 -07:00
Vinay Rao
58d8a7dac5 Merge pull request #1420 from workingmonk/feature/dth_migtration_step0_prod
CHANGE-751 Step 0 of migrating the deprecated DTH over to the new DTHs
2016-11-02 14:12:30 -07:00
Vinay Rao
a98d3dc2d6 CHANGE-751 Step 0 of migrating the deprecated DTH over to the new DTHs 2016-11-02 08:06:10 -07:00
David Sainte-Claire
3a377ba147 added code formatting based on pull request comments 2016-11-01 23:07:27 -07:00
Vinay Rao
2d25a0e63f Merge pull request #1414 from SmartThingsCommunity/master
Rolling up master to staging for next week deploy
2016-11-01 14:27:41 -07:00
Vinay Rao
633a179074 Merge pull request #1413 from SmartThingsCommunity/staging
Rolling down staging changes to master
2016-11-01 14:26:07 -07:00
Vinay Rao
49bc42b4ab Merge pull request #1412 from SmartThingsCommunity/revert-1409-revert-1392-revert-1390-revert_lifx_device_watch_staging
Revert "Revert of DVCSMP-2108 Revert of Revert LIFX device watch"
2016-11-01 14:25:10 -07:00
Vinay Rao
d258c46aee Revert "Revert of DVCSMP-2108 Revert of Revert LIFX device watch" 2016-11-01 14:24:10 -07:00
David Sainte-Claire
8a66742bb5 added device watch implementation for ecobee based on the code from lyric 2016-11-01 13:30:35 -07:00
Vinay Rao
add519433c Merge pull request #1410 from SmartThingsCommunity/staging
Rolling up staging to production for deployment
2016-11-01 12:46:47 -07:00
Vinay Rao
f6dcaf6d09 Merge pull request #1409 from SmartThingsCommunity/revert-1392-revert-1390-revert_lifx_device_watch_staging
Revert of DVCSMP-2108 Revert of Revert LIFX device watch
2016-11-01 12:30:56 -07:00
Vinay Rao
b07b34f66c Revert "DVCSMP-2108 Revert of Revert LIFX device watch" 2016-11-01 12:28:47 -07:00
Zach Varberg
96659f0a73 Merge pull request #1399 from varzac/fix-battery-overvoltage-exception
DVCSMP-2177: Treat over voltage as 100% battery level
2016-11-01 08:59:19 -05:00
Zach Varberg
699f80e9f7 Treat over voltage as 100% battery level
Many DTHs that are generating battery events use the same bit of
copy/pasted code and in that code over voltage is sent as a battery
event with a value of "--" however, that non-numeric value results in
stack traces.  Instead we now report over voltage as 100% battery.

This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2177
2016-11-01 08:52:34 -05:00
Jack Chi
d21dfc09fe Merge pull request #1407 from jackchi/revert-chf404422
Revert "[CHF-404] [CHF-422] Health check for Leviton DZPA1 Plug-in Appliance Module and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)"
2016-10-31 16:18:01 -07:00
Jack Chi
d196125092 Revert "[CHF-404] [CHF-422] Health check for Leviton DZPA1 Plug-in Appliance Module and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)" 2016-10-31 16:11:48 -07:00
Lars Finander
44088d626a DVCSMP-2108 LIFX: Add devicewatch support
-Fixed a color state issue introduced by previous PR
-Fixed original LIFX setup state bug
2016-10-31 13:37:19 -06:00
Jack Chi
2966c4d5a1 Merge pull request #1404 from SmartThingsCommunity/revert-1395-health-configure-to-updated
Revert "[CHF-429] Device Health enrollment refactored into updated()"
2016-10-31 12:09:26 -07:00
Jack Chi
3343273d40 Revert "[CHF-429] Device Health enrollment refactored into updated()" 2016-10-31 11:56:02 -07:00
Kevin Shuk
5c70da54a4 Merge pull request #1378 from surfous/DVCSMP-2155-ZigBee-switch-power-dh-hchk
DVCSMP-2155 ZigBee switch power DH health check
2016-10-28 10:44:36 -07:00
Vinay Rao
687c64d29d Merge pull request #1305 from ShilpaMathew/DEVC-489-2
DEVC-489: Add fingerprint for Leviton 73A00-3ZB
2016-10-27 14:51:12 -07:00
Kevin Shuk
c9d1b168f7 DVCSMP-2155 ZigBee switch power DH health check
* Add capability Health Check
* specify 5 minute reporting interval to `zigbee.onOffConfig()`
* set the checkInterval attribute in `configure()` (as it sets the reporting interval)
* implements `ping()` by sending the `zigbee.onOffRefresh()` command

For the Google Home project, supports specifically
* GE/Jasco ZigBee Plug-in Smart Switch (45853)
* GE/Jasco ZigBee In-Wall Smart Switch (45856)

The DH itself supports those, plus the Smartthings Outlet V4
* `fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019"`

and two other generic fingerprints:
* `fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"`
* `fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"`
2016-10-27 14:29:20 -07:00
Jack Chi
2f87309fdf Merge pull request #1383 from pchomal/zigbeeDimmer_power
CHF-435 Implementation of Health Check for Zigbee Dimmer (GE Plug-In/In-Wall Smart Dimmer)
2016-10-26 21:58:51 -07:00
Jack Chi
37524f17b2 Merge pull request #1395 from jackchi/health-configure-to-updated
[CHF-429] Device Health enrollment refactored into updated()
2016-10-26 14:19:22 -07:00
jackchi
47522facc7 [CHF-429] Device Health enrollment refactored into updated() from configure() 2016-10-26 10:04:51 -07:00
Vinay Rao
4363661157 Merge pull request #1394 from SmartThingsCommunity/master
Rolling up master to staging
2016-10-25 15:15:00 -07:00
Vinay Rao
330b41941a Merge pull request #1393 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-10-25 15:14:13 -07:00
Vinay Rao
26d286e0a0 Merge pull request #1392 from SmartThingsCommunity/revert-1390-revert_lifx_device_watch_staging
DVCSMP-2108 Revert of Revert LIFX device watch
2016-10-25 15:12:45 -07:00
Lars Finander
ef2323f1b1 Revert "Revert LIFX device watch" 2016-10-25 16:10:48 -06:00
Vinay Rao
51452bc095 Merge pull request #1391 from SmartThingsCommunity/staging
Rolling up staging for prod deployment
2016-10-25 15:03:50 -07:00
Vinay Rao
b7b29d8dbc Merge pull request #1390 from larsfinander/revert_lifx_device_watch_staging
Revert LIFX device watch
2016-10-25 14:29:34 -07:00
Lars Finander
b8111e8760 Revert LIFX device watch
DVCSMP-2108 LIFX: Add devicewatch support
DVCSMP-2168 LIFX: Send device watch registration events in update()
Left Hue change for second ticket above
2016-10-25 15:17:34 -06:00
Jack Chi
24ea8269a3 Merge pull request #1386 from parijatdas/leviton_dimmer
[CHF-436] Health check for Leviton DZPD3-1LW Plug-in Lamp Dimmer Module (Z-Wave)
2016-10-25 11:10:33 -07:00
Vinay Rao
20df244dca Merge pull request #1388 from varzac/revert-humidity-change
[DPROT-183] Remove read attribute for new mfg code
2016-10-25 10:29:26 -07:00
Zach Varberg
583d42df13 Remove read attribute for new mfg code
Because of the interaction between the DTH running in the cloud and the
one running in appengine, we can't make this change yet as for unupdated
devices the humidity gets replaced with null.  So we back out the call
with the new mfgID as there aren't devices out there yet using it.

This relates to: https://smartthings.atlassian.net/browse/DPROT-183
2016-10-25 09:10:39 -05:00
Parijat Das
f1309b2ee2 Added health check for Leviton DZPD3-1LW Plug-in Lamp Dimmer Module (Z-Wave) along with the README.md 2016-10-25 14:10:58 +05:30
Lars Finander
ec1ae2d0b1 Merge pull request #1387 from larsfinander/DVCSMP-2168_Philips_Hue_LIFX_dw_registration_in_update_staging
DVCSMP-2168 Philips Hue/LIFX: Send device watch registration
2016-10-24 16:53:51 -06:00
Lars Finander
5e48e710d4 DVCSMP-2168 Philips Hue/LIFX: Send device watch registration
-Send device watch register events in update()
2016-10-24 14:44:08 -06:00
Vinay Rao
07c5a3533f Merge pull request #1342 from james-smartthings/DVCSMP-2013-temperature-scale-change-bug-fix
DVCSMP-2013 - Temperature Scale Change bug fix
2016-10-24 11:51:03 -07:00
piyush.c
72b2016b7d CHF-435
Implementation of Health Check for Zigbee Dimmer (GE Plug-In/In-Wall Smart Dimmer)
2016-10-24 14:26:22 +05:30
Lars Finander
7c5438880d Merge pull request #1365 from larsfinander/DVCSMP-2108_LIFX_Add_devicewatch_support_staging
DVCSMP-2108 LIFX: Add devicewatch support
2016-10-19 12:11:32 -06:00
Lars Finander
d9888b3184 Merge pull request #1350 from larsfinander/DVCSMP-2088_Philips_Hue_Add_explicit_online_events_staging
DVCSMP-2088 Philips Hue: Add explicit online events
2016-10-19 12:11:07 -06:00
Lars Finander
b582c3d832 DVCSMP-2108 LIFX: Add devicewatch support 2016-10-19 09:35:11 -06:00
Vinay Rao
c15b1e88e1 Merge pull request #1362 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-10-18 14:03:11 -07:00
Lars Finander
94f57dd249 DVCSMP-2088 Philips Hue: Add explicit online events 2016-10-18 10:59:27 -06:00
James Chen
b12df3f360 Convert setpoints in case of a temperature scale change. Removed useage of convertTemperatureIfNeeded which is an undocumented API
fixed spacing

On temperature change it will update all setpoints in alterSetpoint function

fixed spacing issues

spacing changes

removed comments
2016-10-13 13:10:32 -07:00
marstorp
4b44460b0b CHF-420 Add device health check to Bose DTH 2016-10-11 10:58:49 -07:00
ShilpaMathew
6a1a2b0ed9 Add fingerprint for Leviton 73A00-3ZB 2016-09-27 14:49:25 -07:00
92 changed files with 1908 additions and 3792 deletions

View File

@@ -1,10 +1,10 @@
# SmartThings Public Github Repo
# SmartThings Public GitHub Repo
An official list of SmartApps and Device Types from SmartThings.
Here are some links to help you get started coding right away:
* [Github-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
* [GitHub-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
* [Full Documentation](http://docs.smartthings.com)
* [IDE & Simulator](http://ide.smartthings.com)
* [Community Forums](http://community.smartthings.com)

View File

@@ -0,0 +1,46 @@
/**
* Stateless On/Off Button Tile
*
* Author: Ronald Gouldner
*
* Date: 2015-05-14
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "Stateless On-Off Button Tile", namespace: "gouldner", author: "Ronald Gouldner") {
capability "Actuator"
capability "Switch"
capability "Sensor"
}
// simulator metadata
simulator {
}
// UI tile definitions
tiles {
standardTile("button", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "offReady", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "onReady"
state "onReady", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "offReady"
state "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
state "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
}
main "button"
details "button"
}
}
def parse(String description) {
}
def on() {
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned on"
sendEvent(name: "switch", value: "on")
sendEvent(name: "switch", value: "onReady")
}
def off() {
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned off"
sendEvent(name: "switch", value: "off")
sendEvent(name: "switch", value: "offReady")
}

View File

@@ -15,6 +15,7 @@ metadata {
definition (name: "Aeon Key Fob", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability "Sensor"
capability "Battery"
@@ -118,3 +119,16 @@ def configure() {
log.debug("Sending configuration: $cmd")
return cmd
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -15,6 +15,7 @@ metadata {
definition (name: "Aeon Minimote", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability "Sensor"
@@ -107,3 +108,16 @@ def configure() {
log.debug("Sending configuration: $cmds")
return cmds
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -27,7 +27,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Music Player"
capability "Polling"
capability "Health Check"
/**
* Define all commands, ie, if you have a custom action not
@@ -236,7 +236,33 @@ def parse(String event) {
* @return action(s) to take or null
*/
def installed() {
onAction("refresh")
// Notify health check about this device with timeout interval 12 minutes
sendEvent(name: "checkInterval", value: 12 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
startPoll()
}
/**
* Called by health check if no events been generated in the last 12 minutes
* If device doesn't respond it will be marked offline (not available)
*/
def ping() {
TRACE("ping")
boseSendGetNowPlaying()
}
/**
* Schedule a 2 minute poll of the device to refresh the
* tiles so the user gets the correct information.
*/
def startPoll() {
TRACE("startPoll")
unschedule()
// Schedule 2 minute polling of speaker status (song average length is 3-4 minutes)
def sec = Math.round(Math.floor(Math.random() * 60))
//def cron = "$sec 0/5 * * * ?" // every 5 min
def cron = "$sec 0/2 * * * ?" // every 2 min
log.debug "schedule('$cron', boseSendGetNowPlaying)"
schedule(cron, boseSendGetNowPlaying)
}
/**
@@ -316,14 +342,6 @@ def onAction(String user, data=null) {
return actions
}
/**
* Called every so often (every 5 minutes actually) to refresh the
* tiles so the user gets the correct information.
*/
def poll() {
return boseRefreshNowPlaying()
}
/**
* Joins this speaker into the everywhere zone
*/
@@ -837,6 +855,10 @@ def boseRefreshNowPlaying(delay=0) {
return boseGET("/now_playing")
}
def boseSendGetNowPlaying() {
sendHubCommand(boseGET("/now_playing"))
}
/**
* Requests the list of presets
*
@@ -1014,4 +1036,8 @@ def boseGetDeviceID() {
*/
def getDeviceIP() {
return parent.resolveDNI2Address(device.deviceNetworkId)
}
def TRACE(text) {
log.trace "${text}"
}

View File

@@ -1,156 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* CentraLite Dimmer
*
* Author: SmartThings
* Date: 2013-12-04
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'${currentValue} W'
}
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "Parse description $description"
def name = null
def value = null
if (description?.startsWith("catchall:")) {
def msg = zigbee.parse(description)
log.trace msg
log.trace "data: $msg.data"
} else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Read attr: $description"
if (descMap.cluster == "0006" && descMap.attrId == "0000") {
name = "switch"
value = descMap.value.endsWith("01") ? "on" : "off"
} else {
def reportValue = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
name = "power"
// assume 16 bit signed for encoding and power divisor is 10
value = Integer.parseInt(reportValue, 16) / 10
}
} else if (description?.startsWith("on/off:")) {
log.debug "Switch command"
name = "switch"
value = description?.endsWith(" 1") ? "on" : "off"
}
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
// Commands to device
def on() {
[
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}
def off() {
[
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}
def setLevel(value) {
log.trace "setLevel($value)"
sendEvent(name: "level", value: value)
def level = hexString(Math.round(value * 255/100))
def cmd = "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 2000}"
log.debug cmd
cmd
}
def meter() {
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
}
def refresh() {
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
}
def configure() {
[
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0xB04 {${device.zigbeeId}} {}"
]
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -81,51 +81,47 @@ metadata {
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
List result = []
def descMap = zigbee.parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
List attrData = [[cluster: descMap.cluster ,attrId: descMap.attrId, value: descMap.value]]
descMap.additionalAttrs.each {
attrData << [cluster: descMap.cluster, attrId: it.attrId, value: it.value]
}
attrData.each {
def map = [:]
if (it.cluster == "0201" && it.attrId == "0000") {
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
map.value = getTemperature(it.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
} else if (it.cluster == "0201" && it.attrId == "0011") {
log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value)
map.value = getTemperature(it.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
} else if (it.cluster == "0201" && it.attrId == "0012") {
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
map.value = getTemperature(it.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
} else if (it.cluster == "0201" && it.attrId == "001c") {
log.debug "MODE"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
map.value = getModeMap()[it.value]
} else if (it.cluster == "0202" && it.attrId == "0000") {
log.debug "FAN MODE"
map.name = "thermostatFanMode"
map.value = getFanModeMap()[descMap.value]
map.value = getFanModeMap()[it.value]
}
if (map) {
result << createEvent(map)
}
log.debug "Parse returned $map"
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getModeMap() { [
"00":"off",
"03":"cool",

View File

@@ -16,7 +16,7 @@ metadata {
capability "Switch"
capability "Switch Level"
capability "Button"
capability "Actuator"
capability "Actuator"
//fingerprint deviceId: "0x1200", inClusters: "0x77 0x86 0x75 0x73 0x85 0x72 0xEF", outClusters: "0x26"
}
@@ -74,20 +74,20 @@ def off() {
}
def levelup() {
def curlevel = device.currentValue('level') as Integer
def curlevel = device.currentValue('level') as Integer
if (curlevel <= 90)
setLevel(curlevel + 10);
setLevel(curlevel + 10);
}
def leveldown() {
def curlevel = device.currentValue('level') as Integer
def curlevel = device.currentValue('level') as Integer
if (curlevel >= 10)
setLevel(curlevel - 10)
setLevel(curlevel - 10)
}
def setLevel(value) {
log.trace "setLevel($value)"
sendEvent(name: "level", value: value)
sendEvent(name: "level", value: value)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
@@ -106,11 +106,11 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
if (cmd.upDown == true) {
Integer buttonid = 2
leveldown()
checkbuttonEvent(buttonid)
checkbuttonEvent(buttonid)
} else if (cmd.upDown == false) {
Integer buttonid = 3
levelup()
checkbuttonEvent(buttonid)
checkbuttonEvent(buttonid)
}
}
@@ -140,12 +140,12 @@ def buttonEvent(button) {
def result = []
if (button == 1) {
def mystate = device.currentValue('switch');
if (mystate == "on")
if (mystate == "on")
off()
else
on()
on()
}
updateState("currentButton", "$button")
updateState("currentButton", "$button")
// update the device state, recording the button press
result << createEvent(name: "button", value: "pushed", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
result
@@ -182,3 +182,16 @@ def updateState(String name, String value) {
state[name] = value
device.updateDataValue(name, value)
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 3)
}

View File

@@ -1,6 +1,6 @@
# Connected Cree LED Bulb
Cloud Execution
Works with:
@@ -23,8 +23,10 @@ Works with:
## Device Health
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
Check-in interval = 12 mins
Connected Cree LED Bulb with cloud polling it every __5min__
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Troubleshooting

View File

@@ -82,7 +82,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
/**
@@ -105,7 +105,7 @@ def healthPoll() {
def configure() {
unschedule()
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device
// Device-Watch allows 2 check-in misses from device + ping
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -1,6 +1,6 @@
# Z-wave Dimmer Switch
Local Execution on V2 Hubs
Works with:
@@ -34,6 +34,8 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.

View File

@@ -22,9 +22,10 @@ metadata {
capability "Sensor"
capability "Health Check"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer "
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer "
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer "
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
}
simulator {
@@ -236,17 +237,17 @@ def refresh() {
}
void indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false)
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
}
void indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false)
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
}
void indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false)
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
}

View File

@@ -23,6 +23,7 @@ metadata {
capability "Sensor"
capability "Refresh"
capability "Relative Humidity Measurement"
capability "Health Check"
command "generateEvent"
command "raiseSetpoint"
@@ -38,6 +39,7 @@ metadata {
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "string"
attribute "deviceAlive", "enum", ["true", "false"]
}
tiles {
@@ -120,6 +122,21 @@ metadata {
}
void installed() {
// The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline
// Using 12 minutes because in testing, device health team found that there could be "jitter"
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud"], displayed: false)
}
// Device Watch will ping the device to proactively determine if the device has gone offline
// If the device was online the last time we refreshed, trigger another refresh as part of the ping.
def ping() {
def isAlive = device.currentValue("deviceAlive") == "true" ? true : false
if (isAlive) {
refresh()
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
@@ -148,14 +165,12 @@ def generateEvent(Map results) {
handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
event << [value: sendValue, unit: temperatureScale, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
@@ -166,7 +181,11 @@ def generateEvent(Map results) {
} else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else {
} else if (name == "deviceAlive") {
isChange = isStateChange(device, name, value.toString())
event['isStateChange'] = isChange
event['displayed'] = false
} else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
@@ -253,7 +272,6 @@ void setCoolingSetpoint(setpoint) {
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (coolingSetpoint > maxCoolingSetpoint) {
coolingSetpoint = maxCoolingSetpoint
} else if (coolingSetpoint < minCoolingSetpoint) {
@@ -283,7 +301,6 @@ void setCoolingSetpoint(setpoint) {
}
void resumeProgram() {
log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last()
@@ -354,7 +371,6 @@ def switchFanMode() {
}
def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode"
def returnCommand
@@ -520,63 +536,56 @@ def fanAuto() {
}
def generateSetpointEvent() {
log.debug "Generate SetPoint Event"
def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint")
log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint")
log.debug "Cooling Setpoint = ${coolingSetpoint}"
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if(location.temperatureScale == "C")
{
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
minHeatingSetpoint = roundC(minHeatingSetpoint)
minCoolingSetpoint = roundC(minCoolingSetpoint)
heatingSetpoint = roundC(heatingSetpoint)
coolingSetpoint = roundC(coolingSetpoint)
if(location.temperatureScale == "C") {
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? roundC(convertFtoC(maxHeatingSetpoint)) : roundC(maxHeatingSetpoint)
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? roundC(convertFtoC(maxCoolingSetpoint)) : roundC(maxCoolingSetpoint)
minHeatingSetpoint = minHeatingSetpoint > 40 ? roundC(convertFtoC(minHeatingSetpoint)) : roundC(minHeatingSetpoint)
minCoolingSetpoint = minCoolingSetpoint > 40 ? roundC(convertFtoC(minCoolingSetpoint)) : roundC(minCoolingSetpoint)
heatingSetpoint = heatingSetpoint > 40 ? roundC(convertFtoC(heatingSetpoint)) : roundC(heatingSetpoint)
coolingSetpoint = coolingSetpoint > 40 ? roundC(convertFtoC(coolingSetpoint)) : roundC(coolingSetpoint)
} else {
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? roundC(convertCtoF(maxHeatingSetpoint)) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? roundC(convertCtoF(maxCoolingSetpoint)) : maxCoolingSetpoint
minHeatingSetpoint = minHeatingSetpoint < 40 ? roundC(convertCtoF(minHeatingSetpoint)) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint < 40 ? roundC(convertCtoF(minCoolingSetpoint)) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? roundC(convertCtoF(heatingSetpoint)) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? roundC(convertCtoF(coolingSetpoint)) : coolingSetpoint
}
log.debug "Current Mode = ${mode}"
log.debug "Heating Setpoint = ${heatingSetpoint}"
log.debug "Cooling Setpoint = ${coolingSetpoint}"
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
} else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
} else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
}
}
void raiseSetpoint() {
@@ -585,21 +594,31 @@ void raiseSetpoint() {
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
if (location.temperatureScale == "C") {
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? convertFtoC(maxHeatingSetpoint) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? convertFtoC(maxCoolingSetpoint) : maxCoolingSetpoint
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
} else {
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? convertCtoF(maxHeatingSetpoint) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? convertCtoF(maxCoolingSetpoint) : maxCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
}
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else {
targetvalue = 0
}
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
@@ -622,20 +641,29 @@ void lowerSetpoint() {
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
if (location.temperatureScale == "C") {
minHeatingSetpoint = minHeatingSetpoint > 40 ? convertFtoC(minHeatingSetpoint) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint > 40 ? convertFtoC(minCoolingSetpoint) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
} else {
targetvalue = 0
minHeatingSetpoint = minHeatingSetpoint < 40 ? convertCtoF(minHeatingSetpoint) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint < 40 ? convertCtoF(minCoolingSetpoint) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
}
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
@@ -653,7 +681,6 @@ void lowerSetpoint() {
//called by raiseSetpoint() and lowerSetpoint()
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
if (mode == "off" || mode == "auto") {
@@ -666,6 +693,18 @@ void alterSetpoint(temp) {
def targetHeatingSetpoint
def targetCoolingSetpoint
def temperatureScaleHasChanged = false
if (location.temperatureScale == "C") {
if ( heatingSetpoint > 40.0 || coolingSetpoint > 40.0 ) {
temperatureScaleHasChanged = true
}
} else {
if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) {
temperatureScaleHasChanged = true
}
}
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
@@ -707,17 +746,18 @@ void alterSetpoint(temp) {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
if ( temperatureScaleHasChanged )
generateSetpointEvent()
generateStatusEvent()
}
}
def generateStatusEvent() {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature")
def statusText
log.debug "Generate Status Event for Mode = ${mode}"
@@ -727,36 +767,25 @@ def generateStatusEvent() {
log.debug "HVAC Mode = ${mode}"
if (mode == "heat") {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
} else if (mode == "auto") {
statusText = "Right Now: Auto"
} else if (mode == "off") {
statusText = "Right Now: Off"
} else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat"
} else {
statusText = "?"
}
log.debug "Generate Status Event = ${statusText}"
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
}
@@ -770,7 +799,7 @@ def roundC (tempC) {
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
return ((Math.round(((tempF - 32)*(5/9)) * 2))/2).toDouble()
}
def convertCtoF (tempC) {

View File

@@ -29,10 +29,10 @@
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
capability "Configuration"
capability "Color Control"
capability "Power Meter"
command "getDeviceData"
command "softwhite"
command "daylight"
@@ -54,12 +54,12 @@
command "setAdjustedColor"
command "setWhiteLevel"
command "test"
attribute "whiteLevel", "string"
fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33"
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
@@ -84,7 +84,7 @@
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
}
controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) {
state "whiteLevel", action:"setWhiteLevel", label:'White Level'
}
@@ -183,24 +183,24 @@
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
state "hue", label: 'Hue ${currentValue} '
}
main(["switch"])
details(["switch",
"levelSliderControl",
"rgbSelector",
"whiteSliderControl",
details(["switch",
"levelSliderControl",
"rgbSelector",
"whiteSliderControl",
/*"softwhite",
"daylight",
"warmwhite",
"red",
"green",
"red",
"green",
"blue",
"white",
"cyan",
"magenta",
"orange",
"purple",
"yellow",
"yellow",
"fireplace",
"storm",
"deepfade",
@@ -214,7 +214,7 @@
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
toggleTiles("off") //turn off the hard color tiles
def level = device.latestValue("level")
@@ -223,19 +223,19 @@ def setAdjustedColor(value) {
log.debug "level is: ${level}"
value.level = level
def c = hexToRgb(value.hex)
def c = hexToRgb(value.hex)
value.rh = hex(c.r * (level/100))
value.gh = hex(c.g * (level/100))
value.bh = hex(c.b * (level/100))
setColor(value)
setColor(value)
}
def setColor(value) {
log.debug "setColor: ${value}"
log.debug "hue is: ${value.hue}"
log.debug "saturation is: ${value.saturation}"
if (value.size() < 8)
toggleTiles("off")
@@ -246,22 +246,22 @@ def setColor(value) {
value.gh = hex(rgb.g)
value.bh = hex(rgb.b)
}
if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App)
def rgb = hslToRGB(value.hue, value.saturation, 0.5)
value.hex = rgbToHex(rgb)
value.rh = hex(rgb.r * value.level/100)
value.gh = hex(rgb.g * value.level/100)
value.bh = hex(rgb.b * value.level/100)
value.bh = hex(rgb.b * value.level/100)
}
if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex
def rgbInt = hexToRgb(value.hex)
value.rh = hex(rgbInt.r)
value.gh = hex(rgbInt.g)
value.bh = hex(rgbInt.b)
}
if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level
def rgbInt = hexToRgb(value.hex)
@@ -269,7 +269,7 @@ def setColor(value) {
value.gh = hex(rgbInt.g * value.level/100)
value.bh = hex(rgbInt.b * value.level/100)
}
if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name
def colorData = getColorData(value.colorName)
value.rh = colorData.rh
@@ -277,7 +277,7 @@ def setColor(value) {
value.bh = colorData.bh
value.hex = "#${value.rh}${value.gh}${value.bh}"
}
if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level
def colorData = getColorData(value.colorName)
value.rh = hex(colorData.r * value.level/100)
@@ -285,7 +285,7 @@ def setColor(value) {
value.bh = hex(colorData.b * value.level/100)
value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}"
}
if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255)
value.rh = hex(value.red)
value.gh = hex(value.green)
@@ -299,7 +299,7 @@ def setColor(value) {
value.bh = hex(value.blue * value.level/100)
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
}
sendEvent(name: "hue", value: value.hue, displayed: false)
sendEvent(name: "saturation", value: value.saturation, displayed: false)
sendEvent(name: "color", value: value.hex, displayed: false)
@@ -309,26 +309,26 @@ def setColor(value) {
if (value.switch) {
sendEvent(name: "switch", value: value.switch)
}
sendRGB(value.rh, value.gh, value.bh)
}
def setLevel(level) {
log.debug "setLevel($level)"
if (level == 0) { off() }
else if (device.latestValue("switch") == "off") { on() }
def colorHex = device.latestValue("color")
if (colorHex == null)
colorHex = "#FFFFFF"
def c = hexToRgb(colorHex)
def r = hex(c.r * (level/100))
def g = hex(c.g * (level/100))
def b = hex(c.b * (level/100))
sendEvent(name: "level", value: level)
sendEvent(name: "setLevel", value: level, displayed: false)
sendRGB(r, g, b)
@@ -337,14 +337,14 @@ def setLevel(level) {
def setWhiteLevel(value) {
log.debug "setWhiteLevel: ${value}"
def level = Math.min(value as Integer, 99)
def level = Math.min(value as Integer, 99)
level = 255 * level/99 as Integer
def channel = 0
if (device.latestValue("switch") == "off") { on() }
sendEvent(name: "whiteLevel", value: value)
sendWhite(channel, value)
sendWhite(channel, value)
}
def sendWhite(channel, value) {
@@ -367,20 +367,20 @@ def sendRGBW(redHex, greenHex, blueHex, whiteHex) {
def configure() {
log.debug "Configuring Device For SmartThings Use"
def cmds = []
// send associate to group 3 to get sensor data reported only to hub
cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
//cmds << sendEvent(name: "level", value: 50)
//cmds << on()
//cmds << doColorButton("Green")
delayBetween(cmds, 500)
}
def parse(String description) {
@@ -411,11 +411,11 @@ def parse(String description) {
def getDeviceData() {
def cmd = []
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmd << response(zwave.versionV1.versionGet())
cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet())
delayBetween(cmd, 500)
}
@@ -426,7 +426,7 @@ def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.Manufacturer
log.debug "productTypeId: ${cmd.productTypeId}"
}
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
updateDataValue("applicationVersion", "${cmd.applicationVersion}")
log.debug "applicationVersion: ${cmd.applicationVersion}"
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
@@ -435,13 +435,13 @@ def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map it
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
}
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
log.debug "checksum: ${cmd.checksum}"
log.debug "firmwareId: ${cmd.firmwareId}"
log.debug "manufacturerId: ${cmd.manufacturerId}"
}
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
log.debug "In CapabilityReport"
}
@@ -546,7 +546,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
[name: "indicatorStatus", value: value, display: false]
[name: "indicatorStatus", value: value, displayed: false]
}
*/
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
@@ -557,7 +557,7 @@ def createEvent(physicalgraph.zwave.Command cmd, Map map) {
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
@@ -593,7 +593,7 @@ def refresh() {
* @return none
*/
def updateZwaveParam(params) {
if ( params ) {
if ( params ) {
def pNumber = params.paramNumber
def pSize = params.size
def pValue = [params.value]
@@ -601,9 +601,9 @@ def updateZwaveParam(params) {
def cmds = []
cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
delayBetween(cmds, 1500)
delayBetween(cmds, 1500)
}
}
@@ -612,22 +612,22 @@ def test() {
//value = [hue: 0, saturation: 100, level: 5]
//value = [red: 255, green: 0, blue: 255, level: 60]
//setColor(value)
def cmd = []
if ( !state.cnt ) {
state.cnt = 6
} else {
state.cnt = state.cnt + 1
}
if ( state.cnt > 10 )
state.cnt = 6
// run programmed light show
cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format()
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
delayBetween(cmd, 500)
}
@@ -638,23 +638,23 @@ def colorNameToRgb(color) {
[name:"Soft White", r: 255, g: 241, b: 224 ],
[name:"Daylight", r: 255, g: 255, b: 251 ],
[name:"Warm White", r: 255, g: 244, b: 229 ],
[name:"Red", r: 255, g: 0, b: 0 ],
[name:"Green", r: 0, g: 255, b: 0 ],
[name:"Blue", r: 0, g: 0, b: 255 ],
[name:"Cyan", r: 0, g: 255, b: 255 ],
[name:"Magenta", r: 255, g: 0, b: 33 ],
[name:"Magenta", r: 255, g: 0, b: 33 ],
[name:"Orange", r: 255, g: 102, b: 0 ],
[name:"Purple", r: 170, g: 0, b: 255 ],
[name:"Yellow", r: 255, g: 255, b: 0 ],
[name:"White", r: 255, g: 255, b: 255 ]
]
def colorData = [:]
def colorData = [:]
colorData = colors.find { it.name == color }
colorData
}
@@ -670,7 +670,7 @@ def hexToRgb(colorHex) {
def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
def colorData = [:]
colorData = [r: rrInt, g: ggInt, b: bbInt]
colorData
@@ -681,7 +681,7 @@ def rgbToHex(rgb) {
def g = hex(rgb.g)
def b = hex(rgb.b)
def hexColor = "#${r}${g}${b}"
hexColor
}
@@ -689,11 +689,11 @@ def hslToRGB(float var_h, float var_s, float var_l) {
float h = var_h / 100
float s = var_s / 100
float l = var_l
def r = 0
def g = 0
def b = 0
if (s == 0) {
r = l * 255
g = l * 255
@@ -705,26 +705,26 @@ def hslToRGB(float var_h, float var_s, float var_l) {
} else {
var_2 = (l + s) - (s * l)
}
float var_1 = 2 * l - var_2
r = 255 * hueToRgb(var_1, var_2, h + (1 / 3))
g = 255 * hueToRgb(var_1, var_2, h)
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
}
def rgb = [:]
rgb = [r: r, g: g, b: b]
rgb
rgb
}
def hueToRgb(v1, v2, vh) {
if (vh < 0) { vh += 1 }
if (vh < 0) { vh += 1 }
if (vh > 1) { vh -= 1 }
if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) }
if ((2 * vh) < 1) { return (v2) }
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
return (v1)
}
@@ -735,49 +735,49 @@ def rgbToHSL(rgb) {
def h = 0
def s = 0
def l = 0
def var_min = [r,g,b].min()
def var_max = [r,g,b].max()
def del_max = var_max - var_min
l = (var_max + var_min) / 2
if (del_max == 0) {
h = 0
s = 0
} else {
if (l < 0.5) { s = del_max / (var_max + var_min) }
if (l < 0.5) { s = del_max / (var_max + var_min) }
else { s = del_max / (2 - var_max - var_min) }
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max
if (r == var_max) { h = del_b - del_g }
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
if (r == var_max) { h = del_b - del_g }
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
else if (b == var_max) { h = (2 / 3) + del_g - del_r }
if (h < 0) { h += 1 }
if (h > 1) { h -= 1 }
}
def hsl = [:]
def hsl = [:]
hsl = [h: h * 100, s: s * 100, l: l]
hsl
}
def getColorData(colorName) {
log.debug "getColorData: ${colorName}"
def colorRGB = colorNameToRgb(colorName)
def colorHex = rgbToHex(colorRGB)
def colorHSL = rgbToHSL(colorRGB)
def colorData = [:]
colorData = [h: colorHSL.h,
s: colorHSL.s,
l: device.latestValue("level"),
r: colorRGB.r,
colorData = [h: colorHSL.h,
s: colorHSL.s,
l: device.latestValue("level"),
r: colorRGB.r,
g: colorRGB.g,
b: colorRGB.b,
rh: hex(colorRGB.r),
@@ -785,8 +785,8 @@ def getColorData(colorName) {
bh: hex(colorRGB.b),
hex: colorHex,
alpha: 1]
colorData
colorData
}
def doColorButton(colorName) {
@@ -798,7 +798,7 @@ def doColorButton(colorName) {
def maxLevel = hex(99)
toggleTiles(colorName.toLowerCase().replaceAll("\\s",""))
if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) }
else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) }
else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) }
@@ -808,8 +808,8 @@ def doColorButton(colorName) {
else if ( colorName == "Daylight" ) { String.format("33050400${maxLevel}02${maxLevel}03${maxLevel}04${maxLevel}%02X", 100) }
else {
def c = getColorData(colorName)
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
setColor(newValue)
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
setColor(newValue)
def r = hex(c.r * (level/100))
def g = hex(c.g * (level/100))
def b = hex(c.b * (level/100))
@@ -823,19 +823,19 @@ def toggleTiles(color) {
if ( !state.colorTiles ) {
state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"]
}
def cmds = []
state.colorTiles.each({
if ( it == color ) {
log.debug "Turning ${it} on"
cmds << sendEvent(name: it, value: "on${it}", display: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
cmds << sendEvent(name: it, value: "on${it}", displayed: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
} else {
//log.debug "Turning ${it} off"
cmds << sendEvent(name: it, value: "off${it}", displayed: false)
}
})
delayBetween(cmds, 2500)
}

View File

@@ -87,7 +87,7 @@ metadata {
def parse(String description) {
def resultMap = zigbee.getEvent(description)
if (resultMap) {
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
if (resultMap.name != "level" || resultMap.value != 0) { // Ignore level reports of 0 sent when bulb turns off
sendEvent(resultMap)
}
}
@@ -99,7 +99,7 @@ def parse(String description) {
def poll() {
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
]
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
@@ -188,25 +188,22 @@ def updated() {
}
def on() {
state.trigger = "on/off"
zigbee.on()
}
def off() {
state.trigger = "on/off"
zigbee.off()
}
def refresh() {
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
]
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
}
def setLevel(value) {
state.trigger = "setLevel"
def cmd
def delayForRefresh = 500
if (dimRate && (state?.rate != null)) {

View File

@@ -1,351 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* GE/Jasco ZigBee Dimmer
*
* Author: SmartThings
* Date: 2015-07-01
*/
metadata {
definition (name: "GE ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Switch Level"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "level", label: 'Level ${currentValue}%'
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
state "power", label:'${currentValue} W'
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "level", "power","levelSliderControl","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// Commands to device
def zigbeeCommand(cluster, attribute){
["st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"]
}
def off() {
zigbeeCommand("6", "0")
}
def on() {
zigbeeCommand("6", "1")
}
def setLevel(value) {
value = value as Integer
if (value == 0) {
off()
}
else {
sendEvent(name: "level", value: value)
setLevelWithRate(value, "0000") + ["delay 1000"] + on() //value is between 0 to 100; GE does NOT switch on if OFF
}
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
]
}
def configure() {
onOffConfig() + levelConfig() + powerConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){
isDescriptionLevel(descMap)
}
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
isDescriptionPower(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
//@return - false or "success" or level [0-100]
def isDescriptionLevel(descMap) {
def dimmerValue = -1
if (descMap.cluster == "0008"){
//TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message
def value = convertHexToInt(descMap.value)
dimmerValue = Math.round(value * 100 / 255)
if(dimmerValue==0 && value > 0) {
dimmerValue = 1 //handling for non-zero hex value less than 3
}
}
else if(descMap.clusterId == "0008") {
if(descMap.command=="0B"){
return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent.
}
else if(descMap.command=="07"){
return [type: "update", value : "level (0008) capability configured successfully"]
}
}
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return "false"
}
}
def isDescriptionPower(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0702") {
if (descMap.attrId == "0400") {
powerValue = convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0702") {
if(descMap.command=="07"){
return [type: "update", value : "power (0702) capability configured successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
//min change in value is 05
def powerConfig() {
[
//Meter (Power) Reporting
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setLevelWithRate(level, rate) {
if(rate == null){
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
["st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -1,284 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* GE/Jasco ZigBee Switch
*
* Author: SmartThings
* Date: 2015-07-01
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "GE ZigBee Switch", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("power", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "power", label:'${currentValue} Watts'
}
main "switch"
details(["switch", "power", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// Commands to device
def zigbeeCommand(cluster, attribute){
"st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"
}
def off() {
zigbeeCommand("6", "0")
}
def on() {
zigbeeCommand("6", "1")
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
]
}
def configure() {
onOffConfig() + powerConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
isDescriptionPower(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionPower(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0702") {
if (descMap.attrId == "0400") {
powerValue = convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0702") {
if(descMap.command=="07"){
return [type: "update", value : "power (0702) capability configured successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
//min change in value is 05
def powerConfig() {
[
//Meter (Power) Reporting
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -17,6 +17,7 @@ metadata {
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Light"
command "setAdjustedColor"
command "reset"
@@ -57,7 +58,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
@@ -172,6 +173,3 @@ def verifyPercent(percent) {
}
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -45,7 +45,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
@@ -87,6 +87,3 @@ def parse(description) {
results
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -18,6 +18,7 @@ metadata {
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Light"
command "setAdjustedColor"
command "reset"
@@ -66,7 +67,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
@@ -188,6 +189,3 @@ def verifyPercent(percent) {
}
}
def ping() {
log.trace "${parent.ping(this)}"
}

View File

@@ -14,7 +14,8 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Health Check"
capability "Light"
command "refresh"
}
@@ -50,7 +51,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
@@ -93,6 +94,3 @@ void refresh() {
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -16,6 +16,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Health Check"
capability "Light"
command "refresh"
}
@@ -55,7 +56,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
@@ -107,6 +108,3 @@ void refresh() {
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -39,7 +39,7 @@ metadata {
}
def generatePresenceEvent(boolean present) {
log.debug "Here in generatePresenceEvent!"
log.info "Life360 generatePresenceEvent($present)"
def value = formatValue(present)
def linkText = getLinkText(device)
def descriptionText = formatDescriptionText(linkText, present)

View File

@@ -11,9 +11,9 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Switch Level" // brightness
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
}
simulator {
@@ -23,7 +23,6 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
@@ -64,12 +63,8 @@ metadata {
}
}
// parse events into attributes
def parse(String description) {
if (description == 'updated') {
return // don't poll when config settings is being updated as it may time out
}
poll()
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
}
// handle commands
@@ -141,7 +136,6 @@ def setLevel(percentage) {
percentage = 1 // clamp to 1%
}
if (percentage == 0) {
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
@@ -193,14 +187,17 @@ def off() {
return []
}
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def refresh() {
log.debug "Executing 'refresh'"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
state.online = false
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
log.warn "$device is Offline"
return []
} else if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
@@ -209,19 +206,20 @@ def poll() {
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "switch", value: data.power)
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "saturation", value: data.color.saturation * 100)
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
sendEvent(name: "model", value: data.product.name)
return []
if (data.connected) {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is Online"
} else {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.warn "$device is Offline"
}
def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {

View File

@@ -10,9 +10,9 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Switch Level" // brightness
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
}
simulator {
@@ -22,13 +22,12 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
@@ -53,15 +52,10 @@ metadata {
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
// parse events into attributes
def parse(String description) {
if (description == 'updated') {
return // don't poll when config settings is being updated as it may time out
}
poll()
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
}
// handle commands
@@ -71,7 +65,6 @@ def setLevel(percentage) {
percentage = 1 // clamp to 1%
}
if (percentage == 0) {
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
@@ -123,14 +116,17 @@ def off() {
return []
}
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def refresh() {
log.debug "Executing 'refresh'"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
state.online = false
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
log.warn "$device is Offline"
return []
} else if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
@@ -138,16 +134,17 @@ def poll() {
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "switch", value: data.power)
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: data.product.name)
return []
}
def refresh() {
log.debug "Executing 'refresh'"
poll()
if (data.connected) {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is Online"
} else {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.warn "$device is Offline"
}
}
def selector() {

View File

@@ -43,8 +43,8 @@ def parse(String description) {
}
def push() {
sendEvent(name: "switch", value: "on", isStateChange: true, display: false)
sendEvent(name: "switch", value: "off", isStateChange: true, display: false)
sendEvent(name: "switch", value: "on", isStateChange: true, displayed: false)
sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
sendEvent(name: "momentary", value: "pushed", isStateChange: true)
}

View File

@@ -1,6 +1,6 @@
# Nyce Door/Window Sensor (Open/Close Sensor)
Cloud Execution
Works with:
@@ -23,7 +23,11 @@ Works with:
## Device Health
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
Nyce Door/Window sensor with reporting interval of 5 min.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Battery Specification

View File

@@ -1,381 +0,0 @@
/*
Osram Lightify Gardenspot Mini RGB
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
*/
metadata {
definition (name: "OSRAM LIGHTIFY Gardenspot mini RGB", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Color Control"
attribute "colorName", "string"
command "setAdjustedColor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "rgbSelector"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.info "description is $description"
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
if(!(description?.startsWith("catchall: 0104 0300"))){
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
}
else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(descMap.attrId == "0001"){ //Saturation Attribute
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Saturation from refresh is $saturationValue"
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def zigbeeOff() {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
zigbeeOff()
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1"
]
}
def configure() {
state.levelValue = 100
log.debug "Configuring Reporting and Bindings."
def configCmds = [
//Switch Reporting
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def zigbeeSetLevel(level) {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << zigbeeOff()
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 255 / 100)
cmds << zigbeeSetLevel(level)
//log.debug cmds
cmds
}
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>100 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)
log.debug "hue value is $hueValue"
def colorName = "Color Mode"
if(hueValue>=0 && hueValue <= 4){
colorName = "Red"
}
else if (hueValue>=5 && hueValue <=21 ){
colorName = "Brick Red"
}
else if (hueValue>=22 && hueValue <=30 ){
colorName = "Safety Orange"
}
else if (hueValue>=31 && hueValue <=40 ){
colorName = "Dark Orange"
}
else if (hueValue>=41 && hueValue <=49 ){
colorName = "Amber"
}
else if (hueValue>=50 && hueValue <=56 ){
colorName = "Gold"
}
else if (hueValue>=57 && hueValue <=65 ){
colorName = "Yellow"
}
else if (hueValue>=66 && hueValue <=83 ){
colorName = "Electric Lime"
}
else if (hueValue>=84 && hueValue <=93 ){
colorName = "Lawn Green"
}
else if (hueValue>=94 && hueValue <=112 ){
colorName = "Bright Green"
}
else if (hueValue>=113 && hueValue <=135 ){
colorName = "Lime"
}
else if (hueValue>=136 && hueValue <=166 ){
colorName = "Spring Green"
}
else if (hueValue>=167 && hueValue <=171 ){
colorName = "Turquoise"
}
else if (hueValue>=172 && hueValue <=187 ){
colorName = "Aqua"
}
else if (hueValue>=188 && hueValue <=203 ){
colorName = "Sky Blue"
}
else if (hueValue>=204 && hueValue <=217 ){
colorName = "Dodger Blue"
}
else if (hueValue>=218 && hueValue <=223 ){
colorName = "Navy Blue"
}
else if (hueValue>=224 && hueValue <=251 ){
colorName = "Blue"
}
else if (hueValue>=252 && hueValue <=256 ){
colorName = "Han Purple"
}
else if (hueValue>=257 && hueValue <=274 ){
colorName = "Electric Indigo"
}
else if (hueValue>=275 && hueValue <=289 ){
colorName = "Electric Purple"
}
else if (hueValue>=290 && hueValue <=300 ){
colorName = "Orchid Purple"
}
else if (hueValue>=301 && hueValue <=315 ){
colorName = "Magenta"
}
else if (hueValue>=316 && hueValue <=326 ){
colorName = "Hot Pink"
}
else if (hueValue>=327 && hueValue <=335 ){
colorName = "Deep Pink"
}
else if (hueValue>=336 && hueValue <=339 ){
colorName = "Raspberry"
}
else if (hueValue>=340 && hueValue <=352 ){
colorName = "Crimson"
}
else if (hueValue>=353 && hueValue <=360 ){
colorName = "Red"
}
colorName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private evenHex(value){
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() % 2 != 0) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
log.trace "setColor($value)"
def max = 0xfe
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
def colorName = getColorName(value.hue)
sendEvent(name: "colorName", value: colorName)
log.debug "color name is : $colorName"
sendEvent(name: "hue", value: value.hue, displayed:false)
sendEvent(name: "saturation", value: value.saturation, displayed:false)
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
if (value.level) {
state.levelValue = value.level
sendEvent(name: "level", value: value.level)
def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level)
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
cmd
}

View File

@@ -1,459 +0,0 @@
/*
Osram Flex RGBW Light Strip
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") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Color Control"
attribute "colorName", "string"
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"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemperature", label: '${currentValue} K'
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp", "rgbSelector"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.info "description is $description"
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
if(!(description?.startsWith("catchall: 0104 0300"))){
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
}
else if (description?.startsWith("read attr -")) { //for values returned after hitting refresh
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
if(descMap.attrId == "0007"){
log.debug "in read attr"
log.debug descMap.value
def tempInMired = convertHexToInt(descMap.value)
def tempInKelvin = Math.round(1000000/tempInMired)
log.trace "temp in kelvin: $tempInKelvin"
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
}
else if(descMap.attrId == "0008"){ //Color mode attribute
if(descMap.value == "00"){
state.colorType = "rgb"
}else if(descMap.value == "02"){
state.colorType = "white"
}
}
else if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(descMap.attrId == "0001"){ //Saturation Attribute
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Saturation from refresh is $saturationValue"
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "description is $description"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def zigbeeOff() {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
zigbeeOff()
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 8"
]
}
def configure() {
state.levelValue = 100
state.colorType = "white"
log.debug "Configuring Reporting and Bindings."
def configCmds = [
//Switch Reporting
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def setColorTemperature(value) {
state?.colorType = "white"
if(value<101){
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
}
def tempInMired = Math.round(1000000/value)
def finalHex = swapEndianHex(hex(tempInMired, 4))
def genericName = getGenericName(value)
log.debug "generic name is : $genericName"
def cmds = []
sendEvent(name: "colorTemperature", value: value, displayed:false)
sendEvent(name: "colorName", value: genericName)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
cmds
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def zigbeeSetLevel(level) {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << zigbeeOff()
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 255 / 100)
cmds << zigbeeSetLevel(level)
//log.debug cmds
cmds
}
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
private getGenericName(value){
def genericName = "White"
if(state?.colorType == "rgb"){
genericName = "Color Mode"
}
else{
if(value < 3300){
genericName = "Soft White"
} else if(value < 4150){
genericName = "Moonlight"
} else if(value < 5000){
genericName = "Cool White"
} else if(value <= 6500){
genericName = "Daylight"
}
}
genericName
}
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>100 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)
log.debug "hue value is $hueValue"
def colorName = "Color Mode"
if(hueValue>=0 && hueValue <= 4){
colorName = "Red"
}
else if (hueValue>=5 && hueValue <=21 ){
colorName = "Brick Red"
}
else if (hueValue>=22 && hueValue <=30 ){
colorName = "Safety Orange"
}
else if (hueValue>=31 && hueValue <=40 ){
colorName = "Dark Orange"
}
else if (hueValue>=41 && hueValue <=49 ){
colorName = "Amber"
}
else if (hueValue>=50 && hueValue <=56 ){
colorName = "Gold"
}
else if (hueValue>=57 && hueValue <=65 ){
colorName = "Yellow"
}
else if (hueValue>=66 && hueValue <=83 ){
colorName = "Electric Lime"
}
else if (hueValue>=84 && hueValue <=93 ){
colorName = "Lawn Green"
}
else if (hueValue>=94 && hueValue <=112 ){
colorName = "Bright Green"
}
else if (hueValue>=113 && hueValue <=135 ){
colorName = "Lime"
}
else if (hueValue>=136 && hueValue <=166 ){
colorName = "Spring Green"
}
else if (hueValue>=167 && hueValue <=171 ){
colorName = "Turquoise"
}
else if (hueValue>=172 && hueValue <=187 ){
colorName = "Aqua"
}
else if (hueValue>=188 && hueValue <=203 ){
colorName = "Sky Blue"
}
else if (hueValue>=204 && hueValue <=217 ){
colorName = "Dodger Blue"
}
else if (hueValue>=218 && hueValue <=223 ){
colorName = "Navy Blue"
}
else if (hueValue>=224 && hueValue <=251 ){
colorName = "Blue"
}
else if (hueValue>=252 && hueValue <=256 ){
colorName = "Han Purple"
}
else if (hueValue>=257 && hueValue <=274 ){
colorName = "Electric Indigo"
}
else if (hueValue>=275 && hueValue <=289 ){
colorName = "Electric Purple"
}
else if (hueValue>=290 && hueValue <=300 ){
colorName = "Orchid Purple"
}
else if (hueValue>=301 && hueValue <=315 ){
colorName = "Magenta"
}
else if (hueValue>=316 && hueValue <=326 ){
colorName = "Hot Pink"
}
else if (hueValue>=327 && hueValue <=335 ){
colorName = "Deep Pink"
}
else if (hueValue>=336 && hueValue <=339 ){
colorName = "Raspberry"
}
else if (hueValue>=340 && hueValue <=352 ){
colorName = "Crimson"
}
else if (hueValue>=353 && hueValue <=360 ){
colorName = "Red"
}
colorName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private evenHex(value){
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() % 2 != 0) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
state?.colorType = "rgb"
log.trace "setColor($value)"
def max = 0xfe
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
def colorName = getColorName(value.hue)
log.debug "color name is : $colorName"
sendEvent(name: "colorName", value: colorName)
sendEvent(name: "colorTemperature", value: "--", displayed:false)
sendEvent(name: "hue", value: value.hue, displayed:false)
sendEvent(name: "saturation", value: value.saturation, displayed:false)
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
if (value.level) {
state.levelValue = value.level
sendEvent(name: "level", value: value.level)
def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level)
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
cmd
}

View File

@@ -1,252 +0,0 @@
/*
Osram Tunable White 60 A19 bulb
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 Tunable White 60W", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Refresh"
capability "Sensor"
attribute "colorName", "string"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemperature", label: '${currentValue} K'
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.trace description
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
log.debug descMap.value
def tempInMired = convertHexToInt(descMap.value)
def tempInKelvin = Math.round(1000000/tempInMired)
log.trace "temp in kelvin: $tempInKelvin"
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7"
]
}
def configure() {
state.levelValue = 100
log.debug "Configuring Reporting and Bindings."
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return onOffConfig() + levelConfig() + configCmds + refresh() // send refresh cmds as part of config
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 300 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setColorTemperature(value) {
if(value<101){
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
}
def tempInMired = Math.round(1000000/value)
def finalHex = swapEndianHex(hex(tempInMired, 4))
def genericName = getGenericName(value)
log.debug "generic name is : $genericName"
def cmds = []
sendEvent(name: "colorTemperature", value: value, displayed:false)
sendEvent(name: "colorName", value: genericName)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
cmds
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 254 / 100)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
//log.debug cmds
cmds
}
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
private getGenericName(value){
def genericName = "White"
if(value < 3300){
genericName = "Soft White"
} else if(value < 4150){
genericName = "Moonlight"
} else if(value < 5000){
genericName = "Cool White"
} else if(value <= 6500){
genericName = "Daylight"
}
genericName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}

View File

@@ -69,15 +69,17 @@ metadata {
def parse(String description) {
log.debug "description is $description"
def event = [:]
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
if (finalResult) {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
event = null
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue)
event = createEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
@@ -87,13 +89,14 @@ def parse(String description) {
*/
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
event = createEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
return event
}
// Commands to device
@@ -125,9 +128,9 @@ def setLevel(value) {
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 500"
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 2000"
]
}
@@ -209,13 +212,16 @@ def isKnownDescription(description) {
else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){
isDescriptionPower(descMap)
}
else {
return [:]
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
return [:]
}
}
@@ -249,7 +255,7 @@ def isDescriptionOnOff(descMap) {
return [type: "switch", value : switchValue]
}
else {
return "false"
return [:]
}
}
@@ -276,10 +282,9 @@ def isDescriptionLevel(descMap) {
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return "false"
return [:]
}
}
@@ -301,16 +306,16 @@ def isDescriptionPower(descMap) {
return [type: "power", value : powerValue]
}
else {
return "false"
return [:]
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}
@@ -318,9 +323,9 @@ def onOffConfig() {
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}
@@ -328,9 +333,10 @@ def levelConfig() {
//min change in value is 05
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}
@@ -339,7 +345,10 @@ def setLevelWithRate(level, rate) {
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"
[
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}",
"delay 2000"
]
}
String convertToHexString(value, width=2) {

View File

@@ -51,7 +51,7 @@ def on() {
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
'delay 2000'
]
@@ -62,6 +62,6 @@ def off() {
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
'delay 2000'
]
}

View File

@@ -1,6 +1,6 @@
# SmartPower Outlet
Local Execution on V2 Hubs
Works with:
@@ -23,10 +23,11 @@ Works with:
## Device Health
A Category C1 smart power outlet with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartPower outlet with reporting interval of 5 mins
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __21min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Troubleshooting

View File

@@ -79,6 +79,7 @@ def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getKnownDescription(description)
def event = [:]
//TODO: Remove this after getKnownDescription can parse it automatically
if (!finalResult && description!="updated")
@@ -88,10 +89,11 @@ def parse(String description) {
log.info "final result = $finalResult"
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
event = null
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
event = createEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
@@ -100,7 +102,7 @@ def parse(String description) {
}
else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
event = createEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
}
}
else {
@@ -109,10 +111,11 @@ def parse(String description) {
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
event = createEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
event = null
}
}
else {
@@ -120,6 +123,7 @@ def parse(String description) {
log.debug "${cluster}"
}
}
return event
}
def off() {
@@ -141,9 +145,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
@@ -153,9 +157,10 @@ def configure() {
//min change in value is 01
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}

View File

@@ -86,7 +86,7 @@ metadata {
def parse(String description) {
log.debug "parse($description)"
def results = null
def results = [:]
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
// Ignore this in favor of orientation-based state

View File

@@ -1,6 +1,6 @@
# Smartsense Moisture Sensor
Local Execution on V2 Hubs
Works with:
@@ -23,10 +23,11 @@ Works with:
## Device Health
A Category C2 moisture sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartSense Moisture sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification

View File

@@ -29,9 +29,10 @@ metadata {
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
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,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
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,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-L", deviceJoinName: "Iris Smart Water Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
}
@@ -102,7 +103,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : [:]
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -128,7 +129,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
@@ -202,48 +203,37 @@ private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'battery',
value: '--',
translatable: true
]
def result = [:]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) 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 (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ 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
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
}
}
return result
@@ -296,17 +286,17 @@ def ping() {
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 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
@@ -319,10 +309,10 @@ def enrollResponse() {
[
//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",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

View File

@@ -1,6 +1,6 @@
# Smartsense Motion Sensor
Local Execution on V2 Hubs
Works with:
@@ -22,10 +22,12 @@ Works with:
## Device Health
A Category C2 motion sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartSense Motion sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification

View File

@@ -106,7 +106,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : [:]
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -132,7 +132,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
@@ -220,48 +220,35 @@ private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'battery',
value: '--',
translatable: true
]
def result = [:]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) 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 (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ 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
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
def value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
}
}
@@ -311,17 +298,17 @@ def ping() {
def refresh() {
log.debug "refresh called"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
@@ -334,10 +321,10 @@ def enrollResponse() {
[
//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",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

View File

@@ -1,309 +0,0 @@
/**
* SmartSense Motion/Temp Sensor
*
* Copyright 2014 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.
*
*/
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
capability "Configuration"
capability "Battery"
capability "Temperature Measurement"
capability "Refresh"
capability "Sensor"
command "enrollResponse"
}
simulator {
status "active": "zone report :: type: 19 value: 0031"
status "inactive": "zone report :: type: 19 value: 0030"
}
preferences {
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
}
tiles(scale: 2) {
multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
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"
}
main(["motion", "temperature"])
details(["motion", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(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
}
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
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
}
}
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
}
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)
resultMap = getTemperatureResult(value)
}
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)
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
log.debug rawValue
def result = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
def descriptionText
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)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
return result
}
private Map getTemperatureResult(value) {
log.debug 'TEMP'
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,
unit: temperatureScale
]
}
private Map getMotionResult(value) {
log.debug 'motion'
String linkText = getLinkText(device)
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
return [
name: 'motion',
value: value,
descriptionText: descriptionText
]
}
def refresh() {
log.debug "refresh called"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}
def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return refresh() + configCmds // 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() {
new BigInteger(device.endpointId, 16).toString()
}
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

@@ -44,7 +44,7 @@ metadata {
}
def parse(String description) {
def results
def results = [:]
if (isZoneType19(description) || !isSupportedDescription(description)) {
results = parseBasicMessage(description)
}
@@ -57,21 +57,24 @@ def parse(String description) {
private Map parseBasicMessage(description) {
def name = parseName(description)
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = value
def isStateChange = isStateChange(device, name, value)
def results = [:]
if (name != null) {
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = value
def isStateChange = isStateChange(device, name, value)
def results = [
name: name,
value: value,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
results = [
name : name,
value : value,
linkText : linkText,
descriptionText: descriptionText,
handlerName : handlerName,
isStateChange : isStateChange,
displayed : displayed(description, isStateChange)
]
}
log.debug "Parse returned $results.descriptionText"
return results
}

View File

@@ -1,6 +1,6 @@
# Smartsense Multi Sensor
Local Execution on V2 Hubs
Works with:
@@ -26,10 +26,11 @@ Works with:
## Device Health
A Category C2 multi sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartSense Multi sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification

View File

@@ -127,7 +127,7 @@ def parse(String description) {
map = parseIasMessage(description)
}
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : [:]
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -161,7 +161,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if(cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
@@ -281,47 +281,35 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def result = [
name: 'battery',
value: '--',
translatable: true
]
def result = [:]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) 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 (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ 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
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
}
}
@@ -351,7 +339,7 @@ private Map getContactResult(value) {
log.debug "Contact: ${device.displayName} value = ${value}"
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
return [name: 'status', value: value, descriptionText: descriptionText, translatable: true]
}
private getAccelerationResult(numValue) {
@@ -412,9 +400,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting"
@@ -440,10 +428,10 @@ def enrollResponse() {
[
//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",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

View File

@@ -1,334 +0,0 @@
/**
* SmartSense Open/Closed Accelerometer Sensor
*
* Copyright 2014 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.
*
*/
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Acceleration Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
}
simulator {
}
preferences {
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
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
}
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
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"
}
main (["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(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
}
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
case 0xFC02:
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
}
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
}
private int getHumidity(value) {
return Math.round(Double.parseDouble(value))
}
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)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0002") {
Integer.parseInt(descMap.value,8)
}
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: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
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)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
return result
}
private Map getTemperatureResult(value) {
log.debug 'TEMP'
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,
unit: temperatureScale
]
}
private Map getContactResult(value) {
log.debug 'Contact Status'
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
return [
name: 'contact',
value: value,
descriptionText: descriptionText
]
}
private getAccelerationResult(numValue) {
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,
//unit: null,
//linkText: linkText,
descriptionText: descriptionText,
//handlerName: value,
isStateChange: isStateChange
// displayed: displayed(description, isStateChange)
]
}
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 0xFC02 2", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return refresh() + configCmds // 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() {
new BigInteger(device.endpointId, 16).toString()
}
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

@@ -1,6 +1,6 @@
# Smartsense Open/Closed Sensor
Local Execution on V2 Hubs
Works with:
@@ -24,10 +24,11 @@ Works with:
## Device Health
A Category C2 open/closed sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartSense Open Closed sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification

View File

@@ -93,7 +93,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : [:]
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -119,7 +119,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
@@ -196,25 +196,19 @@ private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def result = [:]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (!(rawValue == 0 || rawValue == 255)) {
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
@@ -258,17 +252,17 @@ def ping() {
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 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting, IAS CIE, and Bindings."
@@ -283,10 +277,10 @@ def enrollResponse() {
[
//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",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

View File

@@ -1,6 +1,6 @@
# SmartSense Temp/Humidity Sensor
Local Execution on V2 Hubs
Works with:
@@ -24,10 +24,11 @@ Works with:
## Device Health
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
SmartSense Temp/Humidity Sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkIntervalr 5 min interval is confirmed
## Battery Specification

View File

@@ -84,7 +84,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
return map ? createEvent(map) : null
return map ? createEvent(map) : [:]
}
private Map parseCatchAllMessage(String description) {
@@ -103,7 +103,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
@@ -207,25 +207,20 @@ private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def result = [:]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (!(rawValue == 0 || rawValue == 255)) {
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
@@ -264,21 +259,20 @@ def refresh()
{
log.debug "refresh temperature, humidity, and battery"
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020)
}
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity

View File

@@ -15,9 +15,10 @@ metadata {
definition (name: "Simulated Minimote", namespace: "smartthings/testing", author: "SmartThings") {
capability "Actuator"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability "Sensor"
command "push1"
command "push2"
command "push3"
@@ -45,42 +46,42 @@ metadata {
}
standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Push 1", backgroundColor: "#ffffff", action: "push1"
}
}
standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Push 2", backgroundColor: "#ffffff", action: "push2"
}
}
standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Push 3", backgroundColor: "#ffffff", action: "push3"
}
}
standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Push 4", backgroundColor: "#ffffff", action: "push4"
}
}
standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
}
}
standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Hold 1", backgroundColor: "#ffffff", action: "hold1"
}
}
standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Hold 2", backgroundColor: "#ffffff", action: "hold2"
}
}
standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
}
}
standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Hold 3", backgroundColor: "#ffffff", action: "hold3"
}
}
standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
state "default", label: "Hold 4", backgroundColor: "#ffffff", action: "hold4"
}
}
main "button"
details(["push1","push2","button","push3","push4","dummy1","hold1","hold2","dummy2","hold3","hold4"])
}
}
def parse(String description) {
}
def push1() {
@@ -125,3 +126,15 @@ private hold(button) {
sendEvent(name: "button", value: "held", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was held", isStateChange: true)
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -12,7 +12,7 @@
*
*/
metadata {
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
capability "Switch"
capability "Relay Switch"
@@ -39,9 +39,7 @@ metadata {
}
}
def parse(String description) {
def pair = description.split(":")
createEvent(name: pair[0].trim(), value: pair[1].trim())
def parse(description) {
}
def on() {

View File

@@ -1,6 +1,6 @@
# Tyco Door Window Sensor
Cloud Execution
Works with:
@@ -23,10 +23,11 @@ Works with:
## Device Health
Contact sensor with maxReportTime of 5 mins.
Tyco Door Window Sensor with reporting interval of 5 mins.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 min
* __12min__ checkInterval
## Battery Specification

View File

@@ -181,22 +181,17 @@ private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def result = [:]
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (!(rawValue == 0 || rawValue == 255)) {
def volts = rawValue / 10
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result

View File

@@ -86,7 +86,7 @@ def parse(String description) {
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"

View File

@@ -78,7 +78,7 @@ def parse(String description) {
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()

View File

@@ -84,7 +84,7 @@ def parse(String description) {
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
@@ -208,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}")
@@ -291,4 +291,4 @@ User-Agent: CyberGarage-HTTP/1.0
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
}

View File

@@ -19,6 +19,7 @@ metadata {
capability "Actuator"
capability "Battery"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability "Refresh"
capability "Sensor"
@@ -27,8 +28,8 @@ metadata {
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
}
simulator {}

View File

@@ -0,0 +1,2 @@
.st-ignore
README.md

View File

@@ -0,0 +1,41 @@
# GE Plug-In/In-Wall Smart Dimmer (ZigBee)
Cloud Execution
Works with:
* [GE In-Wall Smart Dimmer (ZigBee)](https://shop.smartthings.com/#!/products/ge-in-wall-smart-dimmer-switch)
* [GE Plug-In Smart Dimmer (ZigBee)](https://www.smartthings.com/works-with-smartthings/ge/ge-plug-in-smart-dimmer-zigbee)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#Troubleshooting)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Power Meter** - ability to check the power meter(energy consumption) of device
* **Sensor** - represents the device sensor capability
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Zigbee Power Dimmer with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [GE Z-Wave In-Wall Smart Dimmer (GE 45857) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204988564-GE-In-Wall-Smart-Dimmer-45857GE-ZigBee-)
* [GE Zigbee Plug-in Smart Dimmer (GE 45852) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205239280-GE-Plug-In-Smart-Dimmer-45852GE-ZigBee-)

View File

@@ -21,6 +21,7 @@ metadata {
capability "Sensor"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
@@ -70,8 +71,20 @@ def parse(String description) {
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
}
@@ -87,11 +100,22 @@ def setLevel(value) {
zigbee.setLevel(value)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
refresh()
}

View File

@@ -1,10 +1,11 @@
# OSRAM Lightify LED On/Off/Dim
# Zigbee Dimmer
Cloud Execution
Works with:
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
* [WeMo LED Bulb](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
## Table of contents
@@ -23,14 +24,16 @@ Works with:
## Device Health
A Category C1 Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
ZigBee Dimmer with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
* [OSRAM Lightify LED On/Off/Dim Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
* [WeMo LED Bulb Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)

View File

@@ -23,9 +23,15 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart BR30 Soft White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G13", deviceJoinName: "Sengled Element Classic"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL6HD", deviceJoinName: "Leviton Dimmer Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL3HL", deviceJoinName: "Leviton Lumina RF Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL1KD", deviceJoinName: "Leviton Lumina RF Dimmer Switch"
}
tiles(scale: 2) {
@@ -102,9 +108,9 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()

View File

@@ -1,243 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
//@Deprecated - Moved to zll-rgbw-bulb
/* Philips Hue (via Zigbee)
Capabilities:
Actuator
Color Control
Configuration
Polling
Refresh
Sensor
Switch
Switch Level
Custom Commands:
setAdjustedColor
*/
metadata {
definition (name: "Zigbee Hue Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Switch"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000", outClusters: "0019"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
main(["switch"])
details(["switch", "levelSliderControl", "rgbSelector", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.trace description
if (description?.startsWith("catchall:")) {
def msg = zigbee.parse(description)
//log.trace msg
//log.trace "data: $msg.data"
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
// just assume it works for now
log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
}
def off() {
// just assume it works for now
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def setHue(value) {
def max = 0xfe
log.trace "setHue($value)"
sendEvent(name: "hue", value: value)
def scaledValue = Math.round(value * max / 100.0)
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledValue)} 00 0000}"
//log.info cmd
cmd
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
log.trace "setColor($value)"
def max = 0xfe
sendEvent(name: "hue", value: value.hue)
sendEvent(name: "saturation", value: value.saturation)
def scaledHueValue = Math.round(value.hue * max / 100.0)
def scaledSatValue = Math.round(value.saturation * max / 100.0)
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledHueValue)} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledSatValue)} 0000}"
if (value.level != null) {
cmd << "delay 150"
cmd.addAll(setLevel(value.level))
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
log.info cmd
cmd
}
def setSaturation(value) {
def max = 0xfe
log.trace "setSaturation($value)"
sendEvent(name: "saturation", value: value)
def scaledValue = Math.round(value * max / 100.0)
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledValue)} 0000}"
//log.info cmd
cmd
}
def refresh() {
"st rattr 0x${device.deviceNetworkId} 1 6 0"
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def setLevel(value) {
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: value)
def level = hexString(Math.round(value * 255/100))
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
//log.debug cmds
cmds
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}

View File

@@ -28,8 +28,8 @@ metadata {
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
}
// UI tile definitions

View File

@@ -1,6 +1,6 @@
# OSRAM LIGHTIFY LED RGBW Bulb
Cloud Execution
Works with:
@@ -27,11 +27,10 @@ Works with:
## Device Health
A Category C6 OSRAM LIGHTIFY LED RGBW Bulb with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
OSRAM LIGHTIFY LED RGBW Bulb with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Troubleshooting

View File

@@ -32,11 +32,12 @@ metadata {
attribute "colorName", "string"
command "setGenericName"
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"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "SYLVANIA Smart Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "SYLVANIA Smart A19 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "SYLVANIA Smart BR30 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "SYLVANIA Smart RT5/6 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY FLEX OUTDOOR RGBW", deviceJoinName: "SYLVANIA Smart Outdoor RGBW Flex"
}
// UI tile definitions
@@ -143,9 +144,9 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh()

View File

@@ -20,6 +20,7 @@ metadata {
capability "Power Meter"
capability "Sensor"
capability "Switch"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
@@ -77,10 +78,28 @@ def on() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
Integer reportIntervalMinutes = 5
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig(0,reportIntervalMinutes * 60) + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
refresh()
log.debug "in configure()"
return configureHealthCheck()
}
def configureHealthCheck() {
Integer hcIntervalMinutes = 12
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
return refresh()
}
def updated() {
log.debug "in updated()"
// updated() doesn't have it's return value processed as hub commands, so we have to send them explicitly
def cmds = configureHealthCheck()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it)) }
}
def ping() {
return zigbee.onOffRefresh()
}

View File

@@ -21,6 +21,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "000A", manufacturer: "HAI", model: "65A21-1", deviceJoinName: "Leviton Wireless Load Control Module-30amp"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL15A", deviceJoinName: "Leviton Lumina RF Plug-In Appliance Module"
}
// simulator metadata

View File

@@ -1,10 +1,11 @@
# OSRAM Lightify Tunable 60 White
# ZigBee White Color Temperature Bulb
Cloud Execution
Works with:
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
* [OSRAM LIGHTIFY RT5/6 Tunable White](https://www.smartthings.com/works-with-smartthings/light-bulbs/osram-lightify-rt56-tunable-white)
## Table of contents
@@ -24,14 +25,15 @@ Works with:
## Device Health
A Category C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
Zigbee Bulb with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
*__12min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)
* [OSRAM Lightify Tunable 60 White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)
* [OSRAM LIGHTIFY RT5/6 Tunable White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/214191863-How-to-connect-OSRAM-LIGHTIFY-Bulbs)

View File

@@ -32,11 +32,12 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "SYLVANIA Smart BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "SYLVANIA Smart RT5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "SYLVANIA Smart A19 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element plus"
}
// UI tile definitions
@@ -126,9 +127,9 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh()

View File

@@ -89,7 +89,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def refresh() {
@@ -127,8 +127,8 @@ def configureHealthCheck() {
def configure() {
log.debug "configure()"
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
configureHealthCheck()
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def updated() {

View File

@@ -115,7 +115,7 @@ def refreshAttributes() {
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){

View File

@@ -27,6 +27,10 @@ metadata {
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 RGBW"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "PAR 16 50 RGBW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY RGBW PAR 16 50"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenpole RGBW-Lightify", deviceJoinName: "OSRAM LIGHTIFY Gardenpole RGBW"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Outdoor Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Outdoor Flex RGBW"
}
// UI tile definitions
@@ -131,7 +135,7 @@ def setColorTemperature(value) {
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){

View File

@@ -32,6 +32,7 @@ metadata {
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"PAR16 50 TW", deviceJoinName: "OSRAM LIGHTIFY LED PAR16 50 Tunable White"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
}
// UI tile definitions
@@ -89,7 +90,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh()
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def refresh() {

View File

@@ -0,0 +1,2 @@
.st-ignore
README.md

View File

@@ -0,0 +1,43 @@
# Z-wave Dimmer Switch Generic
Cloud Execution
Works with:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-plug-in-lamp-dimmer-module)
* [Leviton Universal Dimmer (DZMX1-LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-universal-dimmer)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Switch Level** - it's defined to accept two parameters, the level and the rate of dimming
* **Actuator** - represents that a Device has commands
* **Health Check** - indicates ability to get device health notifications
* **Switch** - can detect state (possible values: on/off)
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - detects sensor events
## Device Health
Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-wave) and Leviton Universal Dimmer (DZMX1-LZ) (Z-Wave) are polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)

View File

@@ -15,12 +15,19 @@ metadata {
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Health Check"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer"
fingerprint mfr:"001D", prod:"1B03", model:"0334", deviceJoinName: "Leviton Universal Dimmer"
fingerprint mfr:"011A", prod:"0102", model:"0201", deviceJoinName: "Enerwave In-Wall Dimmer"
fingerprint mfr:"001D", prod:"1001", model:"0334", deviceJoinName: "Leviton 3-Speed Fan Controller"
fingerprint mfr:"001D", prod:"0602", model:"0334", deviceJoinName: "Leviton Magnetic Low Voltage Dimmer"
fingerprint mfr:"001D", prod:"0401", model:"0334", deviceJoinName: "Leviton 600W Incandescent Dimmer"
}
simulator {
@@ -68,6 +75,11 @@ metadata {
}
}
def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def parse(String description) {
def result = null
if (description != "updated") {
@@ -185,6 +197,13 @@ def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
log.debug "refresh() is called"
def commands = []

View File

@@ -28,6 +28,7 @@ metadata {
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
fingerprint mfr: "0063", prod: "4953", model: "3133", deviceJoinName: "GE Smart Motion Sensor"
}
simulator {

View File

@@ -81,7 +81,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
[name: "indicatorStatus", value: value, display: false]
[name: "indicatorStatus", value: value, displayed: false]
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {

View File

@@ -1,11 +1,13 @@
# Z-wave Switch
Cloud Execution
Works with:
* [Leviton Appliance Module (DZPA1-1LW)](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
* [Leviton Appliance Module (DZPA1-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-appliance-module)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://www.smartthings.com/works-with-smartthings/outlets/ge-plug-in-outdoor-smart-switch)
* [Leviton Outlet (DZR15-1LZ)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-outlet)
* [Leviton Switch (DZS15-1LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-switch)
## Table of contents
@@ -23,17 +25,21 @@ Works with:
## Device Health
A Category C5 Leviton Appliance Module (DZPA1-1LW) and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) polled by the hub.
Leviton Appliance Module (DZPA1-1LW), GE Plug-In Outdoor Smart Switch (GE 12720), Leviton Outlet (DZR15-1LZ) and Leviton Switch (DZS15-1LZ) (Z-Wave) are polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
* [Leviton Outlet (DZR15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Switch (DZS15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)

View File

@@ -21,8 +21,13 @@ metadata {
capability "Sensor"
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"001D", prod:"1A02", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"0063", prod:"4F50", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"001D", prod:"1A02", model:"0334", deviceJoinName: "Leviton Appliance Module"
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet"
fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch"
fingerprint mfr:"001D", prod:"0301", model:"0334", deviceJoinName: "Leviton 15A Switch"
fingerprint mfr:"011A", prod:"0101", model:"0102", deviceJoinName: "Enerwave On/Off Switch"
fingerprint mfr:"011A", prod:"0101", model:"0603", deviceJoinName: "Enerwave Duplex Receptacle"
}
// simulator metadata

View File

@@ -1,5 +1,6 @@
# Z-Wave Switch
Local Execution on V2 Hubs
Works with:
@@ -34,6 +35,7 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval
## Troubleshooting

View File

@@ -114,7 +114,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
[name: "indicatorStatus", value: value, display: false]
[name: "indicatorStatus", value: value, displayed: false]
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
@@ -174,17 +174,17 @@ def refresh() {
}
void indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false)
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
}
void indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false)
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
}
void indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false)
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
}

View File

@@ -73,7 +73,7 @@ def authPage() {
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section() {
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}", description:description
}
}
} else {
@@ -146,19 +146,24 @@ def callback() {
// log.debug "PARAMS: ${params}"
httpPost(params) { resp ->
try {
httpPost(params) { resp ->
def slurper = new JsonSlurper()
def slurper = new JsonSlurper()
resp.data.each { key, value ->
def data = slurper.parseText(key)
state.refreshToken = data.refresh_token
state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
resp.data.each { key, value ->
def data = slurper.parseText(key)
log.debug "Data: $data"
state.refreshToken = data.refresh_token
state.authToken = data.access_token
//state.accessToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
} catch (Exception e) {
log.debug "callback: Call failed $e"
}
// Handle success and failure here, and render stuff accordingly
if (state.authToken) {
@@ -387,18 +392,18 @@ def getDeviceList() {
state.deviceDetail = [:]
state.deviceState = [:]
apiGet("/api/devicelist") { response ->
apiGet("/api/getstationsdata") { response ->
response.data.body.devices.each { value ->
def key = value._id
deviceList[key] = "${value.station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
}
response.data.body.modules.each { value ->
def key = value._id
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
value.modules.each { value2 ->
def key2 = value2._id
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
state.deviceDetail[key2] = value2
state.deviceState[key2] = value2.dashboard_data
}
}
}
@@ -448,6 +453,7 @@ def listDevices() {
}
def apiGet(String path, Map query, Closure callback) {
if(now() >= state.tokenExpires) {
refreshToken();
}
@@ -467,12 +473,16 @@ def apiGet(String path, Map query, Closure callback) {
} catch (Exception e) {
// This is most likely due to an invalid token. Try to refresh it and try again.
log.debug "apiGet: Call failed $e"
if(refreshToken()) {
log.debug "apiGet: Trying again after refreshing token"
httpGet(params) { response ->
callback.call(response)
}
}
if(refreshToken()) {
log.debug "apiGet: Trying again after refreshing token"
try {
httpGet(params) { response ->
callback.call(response)
}
} catch (Exception f) {
log.debug "apiGet: Call failed $f"
}
}
}
}
@@ -561,4 +571,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}

View File

@@ -1,9 +1,11 @@
/**
* Color Coordinator
* Version 1.0.0 - 7/4/15
* Version 1.1.1 - 11/9/16
* By Michael Struck
*
* 1.0.0 - Initial release
* 1.1.0 - Fixed issue where master can be part of slaves. This causes a loop that impacts SmartThings.
* 1.1.1 - Fix NPE being thrown for slave/master inputs being empty.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
@@ -31,27 +33,35 @@ preferences {
}
def mainPage() {
dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
section("Master Light") {
input "master", "capability.colorControl", title: "Colored Light"
dynamicPage(name: "mainPage", title: "", install: true, uninstall: false) {
def masterInList = slaves?.id?.find{it==master?.id}
if (masterInList) {
section ("**WARNING**"){
paragraph "You have included the Master Light in the Slave Group. This will cause a loop in execution. Please remove this device from the Slave Group.", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/caution.png"
}
}
section("Master Light") {
input "master", "capability.colorControl", title: "Colored Light", required: true
}
section("Lights that follow the master settings") {
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: false
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true
}
section([mobileOnly:true], "Options") {
label(title: "Assign a name", required: false)
input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false
href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions"
}
}
}
page(name: "pageAbout", title: "About ${textAppName()}") {
page(name: "pageAbout", title: "About ${textAppName()}", uninstall: true) {
section {
paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
}
section("Instructions") {
paragraph textHelp()
}
section("Tap button below to remove application"){
}
}
def installed() {
@@ -72,27 +82,61 @@ def init() {
}
//-----------------------------------
def onOffHandler(evt){
if (master.currentValue("switch") == "on"){
slaves?.on()
}
else {
slaves?.off()
}
if (slaves && master) {
if (!slaves?.id.find{it==master?.id}){
if (master?.currentValue("switch") == "on"){
if (randomYes) getRandomColorMaster()
else slaves?.on()
}
else {
slaves?.off()
}
}
}
}
def colorHandler(evt) {
def dimLevel = master.currentValue("level")
def hueLevel = master.currentValue("hue")
def saturationLevel = master.currentValue("saturation")
if (slaves && master) {
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
log.debug "Changing Slave units H,S,L"
def dimLevel = master?.currentValue("level")
def hueLevel = master?.currentValue("hue")
def saturationLevel = master.currentValue("saturation")
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
slaves?.setColor(newValue)
try {
log.debug "Changing Slave color temp"
def tempLevel = master?.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
catch (e){
log.debug "Color temp for master --"
}
}
}
}
def getRandomColorMaster(){
def hueLevel = Math.floor(Math.random() *1000)
def saturationLevel = Math.floor(Math.random() * 100)
def dimLevel = master?.currentValue("level")
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
slaves?.setColor(newValue)
log.debug hueLevel
log.debug saturationLevel
master.setColor(newValue)
slaves?.setColor(newValue)
}
def tempHandler(evt){
if (evt.value != "--") {
def tempLevel = master.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
if (slaves && master) {
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
if (evt.value != "--") {
log.debug "Changing Slave color temp based on Master change"
def tempLevel = master.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
}
}
}
//Version/Copyright/Information/Help
@@ -102,11 +146,11 @@ private def textAppName() {
}
private def textVersion() {
def text = "Version 1.0.0 (07/04/2015)"
def text = "Version 1.1.1 (12/13/2016)"
}
private def textCopyright() {
def text = "Copyright © 2015 Michael Struck"
def text = "Copyright © 2016 Michael Struck"
}
private def textLicense() {
@@ -128,5 +172,5 @@ private def textHelp() {
def text =
"This application will allow you to control the settings of multiple colored lights with one control. " +
"Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+
"including on/off conditions, hue, saturation, level and color temperature."
}
"including on/off conditions, hue, saturation, level and color temperature. Also includes a random color feature."
}

View File

@@ -0,0 +1,461 @@
/**
* OpenT2T SmartApp Test
*
* Copyright 2016 OpenT2T
*
* 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.
*
*/
definition(
name: "OpenT2T SmartApp Test",
namespace: "opent2t",
author: "OpenT2T",
description: "Test app to test end to end SmartThings scenarios via OpenT2T",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
/** --------------------+---------------+-----------------------+------------------------------------
* Device Type | Attribute Name| Commands | Attribute Values
* --------------------+---------------+-----------------------+------------------------------------
* switches | switch | on, off | on, off
* motionSensors | motion | | active, inactive
* contactSensors | contact | | open, closed
* presenceSensors | presence | | present, 'not present'
* temperatureSensors | temperature | | <numeric, F or C according to unit>
* accelerationSensors | acceleration | | active, inactive
* waterSensors | water | | wet, dry
* lightSensors | illuminance | | <numeric, lux>
* humiditySensors | humidity | | <numeric, percent>
* locks | lock | lock, unlock | locked, unlocked
* garageDoors | door | open, close | unknown, closed, open, closing, opening
* cameras | image | take | <String>
* thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint,
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
* | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
* | | emergencyHeat, |
* | | setThermostatMode, |
* | | fanOn, fanAuto, |
* | | fanCirculate, |
* | | setThermostatFanMode |
* --------------------+---------------+-----------------------+------------------------------------
*/
//Device Inputs
preferences {
section("Allow <PLACEHOLDER: Your App Name> to control these things...") {
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false
}
}
def getInputs() {
def inputList = []
inputList += contactSensors ?: []
inputList += garageDoors ?: []
inputList += locks ?: []
inputList += cameras ?: []
inputList += motionSensors ?: []
inputList += presenceSensors ?: []
inputList += switches ?: []
inputList += thermostats ?: []
inputList += waterSensors ?: []
return inputList
}
//API external Endpoints
mappings {
path("/subscriptionURL/:url") {
action:
[
PUT: "updateEndpointURL"
]
}
path("/connectionId/:connId") {
action:
[
PUT: "updateConnectionId"
]
}
path("/devices") {
action:
[
GET: "getDevices"
]
}
path("/devices/:id") {
action:
[
GET: "getDevice"
]
}
path("/update/:id") {
action:
[
PUT: "updateDevice"
]
}
path("/subscription/:id") {
action:
[
POST : "registerDeviceChange",
DELETE: "unregisterDeviceChange"
]
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
registerSubscriptions()
}
def initialize() {
state.connectionId = ""
state.endpointURL = "https://ifs.windows-int.com/v1/cb/81C7E77B-EABC-488A-B2BF-FEC42F0DABD2/notify"
registerSubscriptions()
}
//Subscribe events for all devices
def registerSubscriptions() {
registerChangeHandler(inputs)
}
//Subscribe to events from a list of devices
def registerChangeHandler(myList) {
myList.each { myDevice ->
def theAtts = myDevice.supportedAttributes
theAtts.each { att ->
subscribe(myDevice, att.name, eventHandler)
log.info "Registering ${myDevice.displayName}.${att.name}"
}
}
}
//Endpoints function: Subscribe to events from a specific device
def registerDeviceChange() {
def myDevice = findDevice(params.id)
def theAtts = myDevice.supportedAttributes
try {
theAtts.each { att ->
subscribe(myDevice, att.name, eventHandler)
log.info "Registering ${myDevice.displayName}.${att.name}"
}
return ["succeed"]
} catch (e) {
httpError(500, "something went wrong: $e")
}
}
//Endpoints function: Unsubscribe to events from a specific device
def unregisterDeviceChange() {
def myDevice = findDevice(params.id)
try {
unsubscribe(myDevice)
log.info "Unregistering ${myDevice.displayName}"
return ["succeed"]
} catch (e) {
httpError(500, "something went wrong: $e")
}
}
//When events are triggered, send HTTP post to web socket servers
def eventHandler(evt) {
def evt_device_id = evt.deviceId
def evt_device_value = evt.value
def evt_name = evt.name
def evt_device = evt.device
def evt_deviceType = getDeviceType(evt_device);
def params = [
uri : "${state.endpointURL}/${state.connectionId}",
body: [
name : evt_device.displayName,
id : evt_device.id,
deviceType : evt_deviceType,
manufacturer: evt_device.getManufacturerName(),
model : evt_device.getModelName(),
attributes : deviceAttributeList(evt_device)
]
]
try {
log.trace "POST URI: ${params.uri}"
log.trace "Payload: ${params.body}"
httpPostJson(params) { resp ->
resp.headers.each {
log.debug "${it.name} : ${it.value}"
}
log.trace "response status code: ${resp.status}"
log.trace "response data: ${resp.data}"
}
} catch (e) {
log.debug "something went wrong: $e"
}
}
//Endpoints function: update subcription endpoint url [state.endpoint]
void updateEndpointURL() {
state.endpointURL = params.url
log.info "Updated EndpointURL to ${state.endpointURL}"
}
//Endpoints function: update global variable [state.connectionId]
void updateConnectionId() {
def connId = params.connId
state.connectionId = connId
log.info "Updated ConnectionID to ${state.connectionId}"
}
//Endpoints function: return all device data in json format
def getDevices() {
def deviceData = []
inputs?.each {
def deviceType = getDeviceType(it)
if (deviceType == "thermostat") {
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
} else {
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
}
}
log.debug "getDevices, return: ${deviceData}"
return deviceData
}
//Endpoints function: get device data
def getDevice() {
def it = findDevice(params.id)
def deviceType = getDeviceType(it)
def device
if (deviceType == "thermostat") {
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
} else {
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
}
log.debug "getDevice, return: ${device}"
return device
}
//Endpoints function: update device data
void updateDevice() {
def device = findDevice(params.id)
request.JSON.each {
def command = it.key
def value = it.value
if (command) {
def commandList = mapDeviceCommands(command, value)
command = commandList[0]
value = commandList[1]
if (command == "setAwayMode") {
log.info "Setting away mode to ${value}"
if (location.modes?.find { it.name == value }) {
location.setMode(value)
}
} else if (command == "thermostatSetpoint") {
switch (device.currentThermostatMode) {
case "cool":
log.info "Update: ${device.displayName}, [${command}, ${value}]"
device.setCoolingSetpoint(value)
break
case "heat":
case "emergency heat":
log.info "Update: ${device.displayName}, [${command}, ${value}]"
device.setHeatingSetpoint(value)
break
default:
httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
break
}
} else if (!device) {
log.error "updateDevice, Device not found"
httpError(404, "Device not found")
} else if (!device.hasCommand(command)) {
log.error "updateDevice, Device does not have the command"
httpError(404, "Device does not have such command")
} else {
if (command == "setColor") {
log.info "Update: ${device.displayName}, [${command}, ${value}]"
device."$command"(hex: value)
} else if (value.isNumber()) {
def intValue = value as Integer
log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
device."$command"(intValue)
} else if (value) {
log.info "Update: ${device.displayName}, [${command}, ${value}]"
device."$command"(value)
} else {
log.info "Update: ${device.displayName}, [${command}]"
device."$command"()
}
}
}
}
}
/*** Private Functions ***/
//Return current location mode info
private getLocationModeInfo() {
return [mode: location.mode, supported: location.modes.name]
}
//Map each device to a type given it's capabilities
private getDeviceType(device) {
def deviceType
def caps = device.capabilities
log.debug "capabilities: [${device}, ${caps}]"
log.debug "supported commands: [${device}, ${device.supportedCommands}]"
caps.each {
switch (it.name.toLowerCase()) {
case "switch":
deviceType = "switch"
break
case "switch level":
deviceType = "light"
break
case "contact sensor":
deviceType = "contactSensor"
break
case "garageDoorControl":
deviceType = "garageDoor"
break
case "lock":
deviceType = "lock"
break
case "video camera":
deviceType = "camera"
break
case "motion sensor":
deviceType = "motionSensor"
break
case "presence sensor":
deviceType = "presenceSensor"
break
case "thermostat":
deviceType = "thermostat"
break
case "water sensor":
deviceType = "waterSensor"
break
default:
break
}
}
return deviceType
}
//Return a specific device give the device ID.
private findDevice(deviceId) {
return inputs?.find { it.id == deviceId }
}
//Return a list of device attributes
private deviceAttributeList(device) {
device.supportedAttributes.collectEntries { attribute ->
try {
[(attribute.name): device.currentValue(attribute.name)]
} catch (e) {
[(attribute.name): null]
}
}
}
//Map device command and value.
//input command and value are from UWP,
//returns resultCommand and resultValue that corresponds with function and value in SmartApps
private mapDeviceCommands(command, value) {
log.debug "mapDeviceCommands: [${command}, ${value}]"
def resultCommand = command
def resultValue = value
switch (command) {
case "switch":
if (value == 1 || value == "1" || value == "on") {
resultCommand = "on"
resultValue = ""
} else if (value == 0 || value == "0" || value == "off") {
resultCommand = "off"
resultValue = ""
}
break
// light attributes
case "level":
resultCommand = "setLevel"
resultValue = value
break
case "hue":
resultCommand = "setHue"
resultValue = value
break
case "saturation":
resultCommand = "setSaturation"
resultValue = value
break
case "ct":
resultCommand = "setColorTemperature"
resultValue = value
break
case "color":
resultCommand = "setColor"
resultValue = value
// thermostat attributes
case "hvacMode":
resultCommand = "setThermostatMode"
resultValue = value
break
case "fanMode":
resultCommand = "setThermostatFanMode"
resultValue = value
break
case "awayMode":
resultCommand = "setAwayMode"
resultValue = value
break
case "coolingSetpoint":
resultCommand = "setCoolingSetpoint"
resultValue = value
break
case "heatingSetpoint":
resultCommand = "setHeatingSetpoint"
resultValue = value
break
case "thermostatSetpoint":
resultCommand = "thermostatSetpoint"
resultValue = value
break
// lock attributes
case "locked":
if (value == 1 || value == "1" || value == "lock") {
resultCommand = "lock"
resultValue = ""
} else if (value == 0 || value == "0" || value == "unlock") {
resultCommand = "unlock"
resultValue = ""
}
break
default:
break
}
return [resultCommand, resultValue]
}

View File

@@ -57,7 +57,7 @@ def authPage(){
atomicState.accessToken = state.accessToken
}
def redirectUrl = oauthInitUrl()
def redirectUrl = oauthInitUrl()
def uninstallAllowed = false
def oauthTokenProvided = false
if(atomicState.authToken){
@@ -78,9 +78,9 @@ def authPage(){
}
}else{
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
section(){
section(){
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
}
}
}
@@ -137,36 +137,44 @@ def dock_sensor(device_serial, expected_plant_name) {
contentType: "application/json",
]
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
try {
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
try {
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
}
}
} catch (Exception f) {
log.debug "call failed $f"
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
}
} catch (Exception e) {
log.debug "call failed $e"
}
return true
}
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
def plant_put_params = [
uri : appSettings.https_plantLinkServer,
uri : appSettings.https_plantLinkServer,
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType : "application/json"
]
@@ -174,12 +182,16 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
log.debug "updating plant for - ${expected_plant_name}"
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
def plant_put_body_map = [
name: expected_plant_name
name: expected_plant_name
]
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
plant_put_params["body"] = plant_put_body_json_builder.toString()
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
try {
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
}
} catch (Exception e) {
log.debug "call failed $e"
}
}
}
@@ -198,25 +210,29 @@ def moistureHandler(event){
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0]
def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
try {
httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0]
def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
}
}
}
}
}
} catch (Exception e) {
log.debug "call failed $e"
}
}
}
@@ -235,8 +251,12 @@ def batteryHandler(event){
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
try {
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
}
} catch (Exception e) {
log.debug "call failed $e"
}
}
}
@@ -248,7 +268,7 @@ def getDeviceSerialFromEvent(event){
}
def oauthInitUrl(){
atomicState.oauthInitState = UUID.randomUUID().toString()
atomicState.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [
response_type: "code",
client_id: appSettings.client_id,
@@ -275,8 +295,12 @@ def swapToken(){
]
def jsonMap
httpPost(postParams) { resp ->
jsonMap = resp.data
try {
httpPost(postParams) { resp ->
jsonMap = resp.data
}
} catch (Exception e) {
log.debug "call failed $e"
}
atomicState.refreshToken = jsonMap.refresh_token
@@ -287,33 +311,33 @@ def swapToken(){
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
padding:25px;
}
.flex1 {
width:33%;
float:left;
text-align: center;
}
p {
font-size: 2em;
font-family: Verdana, Geneva, sans-serif;
text-align: center;
color: #777;
}
.container {
padding:25px;
}
.flex1 {
width:33%;
float:left;
text-align: center;
}
p {
font-size: 2em;
font-family: Verdana, Geneva, sans-serif;
text-align: center;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
<br clear="all">
<div class="container">
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
<br clear="all">
</div>
<div class="container">
<p>Your PlantLink Account is now connected to SmartThings!</p>
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
</div>
<p>Your PlantLink Account is now connected to SmartThings!</p>
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
</div>
</body>
</html>
"""

View File

@@ -19,9 +19,9 @@
author: "SmartThings",
description: "Control your Bose SoundTouch speakers",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png",
singleInstance: true
)
@@ -104,7 +104,7 @@ def deviceDiscovery()
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices, submitOnChange: true
}
}
}
@@ -196,6 +196,8 @@ def addDevice(){
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
d.boseSetDeviceID(newDevice.value.deviceID)
log.trace "Created ${d.displayName} with id $dni"
// sync DTH with device, done here as it currently don't work from the DTH's installed() method
d.refresh()
} else {
log.trace "${d.displayName} with id $dni already exists"
}

View File

@@ -842,6 +842,7 @@ private void storeThermostatData(thermostats) {
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
deviceAlive: stat.runtime.connected == true ? "true" : "false",
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,

View File

@@ -8,7 +8,7 @@
* 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
* 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
@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true
singleInstance: true
)
preferences {
@@ -110,7 +110,7 @@ def bridgeLinking() {
if (state.refreshUsernameNeeded) {
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
} else {
paragraphText = "Press the button on your Hue Bridge to setup a link. "
paragraphText = "Press the button on your Hue Bridge to setup a link. "
}
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
@@ -172,18 +172,34 @@ def bulbDiscovery() {
if (existingLightsDescription.isEmpty()) {
existingLightsDescription += it.value
} else {
existingLightsDescription += ", ${it.value}"
existingLightsDescription += ", ${it.value}"
}
}
}
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
if (bulbRefreshCount > 200 && numFound == 0) {
// Time out to avoid endless discovery
state.inBulbDiscovery = false
bulbRefreshCount = 0
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Failed!", nextPage:"", refreshInterval:0, install:true, uninstall: true) {
section("Failed to discover any lights, please try again later. Click Done to exit.") {
//input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
}
section {
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
}
}
section {
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
} else {
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
}
section {
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
}
}
}
}
@@ -198,7 +214,7 @@ void ssdpSubscribe() {
private sendDeveloperReq() {
def token = app.id
def host = getBridgeIP()
def host = getBridgeIP()
sendHubCommand(new physicalgraph.device.HubAction([
method: "POST",
path: "/api",
@@ -209,7 +225,7 @@ private sendDeveloperReq() {
}
private discoverHueBulbs() {
def host = getBridgeIP()
def host = getBridgeIP()
sendHubCommand(new physicalgraph.device.HubAction([
method: "GET",
path: "/api/${state.username}/lights",
@@ -231,8 +247,8 @@ private verifyHueBridge(String deviceNetworkId, String host) {
private verifyHueBridges() {
def devices = getHueBridges().findAll { it?.value?.verified != true }
devices.each {
def ip = convertHexToIP(it.value.networkAddress)
def port = convertHexToInt(it.value.deviceAddress)
def ip = convertHexToIP(it.value.networkAddress)
def port = convertHexToInt(it.value.deviceAddress)
verifyHueBridge("${it.value.mac}", (ip + ":" + port))
}
}
@@ -261,7 +277,7 @@ Map bulbsDiscovered() {
bulbs.each {
def value = "${it.name}"
def key = app.id +"/"+ it.id
logg += "$value - $key, "
logg += "$value - $key, "
bulbmap["${key}"] = value
}
}
@@ -288,22 +304,23 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unsubscribe()
unschedule()
unschedule()
initialize()
}
def initialize() {
log.debug "Initializing"
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
state.bulbRefreshCount = 0
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
state.bulbRefreshCount = 0
state.updating = false
setupDeviceWatch()
if (selectedHue) {
addBridge()
addBulbs()
doDeviceSync()
runEvery5Minutes("doDeviceSync")
addBridge()
addBulbs()
doDeviceSync()
runEvery5Minutes("doDeviceSync")
}
}
@@ -321,6 +338,14 @@ def uninstalled(){
state.username = null
}
private setupDeviceWatch() {
def hub = location.hubs[0]
// Make sure that all child devices are enrolled in device watch
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
}
}
private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType)
@@ -369,7 +394,6 @@ def addBulbs() {
if (d) {
log.debug "created ${d.displayName} with id $dni"
d.completedSetup = true
d.refresh()
}
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
@@ -399,23 +423,23 @@ def addBridge() {
if(vbridge) {
def d = getChildDevice(selectedHue)
if(!d) {
// compatibility with old devices
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
// compatibility with old devices
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
newbridge = false
}
}
}
if (newbridge) {
newbridge = false
}
}
}
if (newbridge) {
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
def idNumber = getBridgeIdNumber(selectedHue)
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
@@ -426,9 +450,11 @@ def addBridge() {
d.completedSetup = true
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
def childDevice = getChildDevice(d.deviceNetworkId)
childDevice?.sendEvent(name: "status", value: "Online")
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber)
childDevice?.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -580,47 +606,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
@Deprecated
def locationHandler(evt) {
def description = evt.description
log.trace "Location: $description"
log.trace "Location: $description"
def hub = evt?.hubId
def parsedEvent = parseLanMessage(description)
def parsedEvent = parseLanMessage(description)
parsedEvent << ["hub":hub]
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
//SSDP DISCOVERY EVENTS
//SSDP DISCOVERY EVENTS
log.trace "SSDP DISCOVERY EVENTS"
def bridges = getHueBridges()
log.trace bridges.toString()
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
//bridge does not exist
//bridge does not exist
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
// update the values
def ip = convertHexToIP(parsedEvent.networkAddress)
def port = convertHexToInt(parsedEvent.deviceAddress)
def host = ip + ":" + port
def ip = convertHexToIP(parsedEvent.networkAddress)
def port = convertHexToInt(parsedEvent.deviceAddress)
def host = ip + ":" + port
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
def dni = "${parsedEvent.mac}"
def d = getChildDevice(dni)
def networkAddress = null
if (!d) {
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
def d = getChildDevice(dni)
def networkAddress = null
if (!d) {
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
d = it
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
doDeviceSync()
}
}
}
doDeviceSync()
}
}
}
} else {
updateBridgeStatus(d)
if (d.getDeviceDataByName("networkAddress")) {
@@ -628,22 +654,22 @@ def locationHandler(evt) {
} else {
networkAddress = d.latestState('networkAddress').stringValue
}
log.trace "Host: $host - $networkAddress"
if(host != networkAddress) {
log.debug "Device's port or ip changed for device $d..."
dstate.ip = ip
dstate.port = port
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
log.trace "Host: $host - $networkAddress"
if(host != networkAddress) {
log.debug "Device's port or ip changed for device $d..."
dstate.ip = ip
dstate.port = port
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
}
} else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) {
log.trace "description.xml response (application/xml)"
log.trace "description.xml response (application/xml)"
def body = new XmlSlurper().parseText(parsedEvent.body)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
@@ -655,7 +681,7 @@ def locationHandler(evt) {
}
}
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
log.trace "description.xml response (application/json)"
log.trace "description.xml response (application/json)"
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
if (body.success != null) {
if (body.success[0] != null) {
@@ -692,7 +718,7 @@ def doDeviceSync(){
poll()
ssdpSubscribe()
discoverBridges()
checkBridgeStatus()
checkBridgeStatus()
}
/**
@@ -705,10 +731,14 @@ def doDeviceSync(){
private void updateBridgeStatus(childDevice) {
// Update activity timestamp if child device is a valid bridge
def vbridges = getVerifiedHueBridges()
def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()}
def vbridge = vbridges.find {
"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()
}
vbridge?.value?.lastActivity = now()
if(vbridge) {
if (vbridge && childDevice?.device?.currentValue("status") == "Offline") {
log.debug "$childDevice is back Online"
childDevice?.sendEvent(name: "status", value: "Online")
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
}
}
@@ -717,29 +747,37 @@ private void updateBridgeStatus(childDevice) {
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
*/
private void checkBridgeStatus() {
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11)
bridges.each {
def d = getChildDevice(it.value.mac)
if(d) {
// Set id number on bridge if not done
if (it.value.idNumber == null) {
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11)
bridges.each {
def d = getChildDevice(it.value.mac)
if (d) {
// Set id number on bridge if not done
if (it.value.idNumber == null) {
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
d.sendEvent(name: "idNumber", value: it.value.idNumber)
}
}
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.value.idNumber is Offline"
d.sendEvent(name: "status", value: "Offline")
if (d.currentStatus == "Online") {
log.warn "$d is Offline"
d.sendEvent(name: "status", value: "Offline")
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
state.bulbs?.each {
it.value.online = false
Calendar currentTime = Calendar.getInstance()
getChildDevices().each {
def id = getId(it)
if (state.bulbs[id]?.online == true) {
state.bulbs[id]?.online = false
state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis()
it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
}
}
}
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else {
} else if (d.currentStatus == "Offline") {
log.debug "$d is back Online"
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
d.sendEvent(name: "status", value: "Online")//setOnline(false)
}
}
@@ -791,24 +829,23 @@ def parse(childDevice, description) {
def parsedEvent = parseLanMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = parsedEvent.headers.toString()
def bodyString = parsedEvent.body.toString()
def bodyString = parsedEvent.body.toString()
if (headerString?.contains("json")) {
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
} catch (all) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
} catch (all) {
log.warn "Parsing Body failed"
}
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
} else {
//put response
return handleCommandResponse(body)
}
}
} else {
}
}
} else {
log.debug "parse - got something other than headers,body..."
return []
}
@@ -822,26 +859,26 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
def events = [:]
// For now, only care about changing color temperature if requested by user
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
if (ct != null && ct != 0 && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
// 153 (6500K) to 500 (2000K)
def temp = (ct == 154) ? 6500 : Math.round(1000000 / ct)
device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"])
// Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary
return
}
}
if (hue != null) {
// 0-65535
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
}
}
if (sat != null) {
// 0-254
def value = Math.round(sat * 100 / 254) as int
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
}
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
}
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
if (xy != null && colormode != "hs") {
@@ -943,12 +980,9 @@ private handleCommandResponse(body) {
updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId)
def id = getId(device)
// If device is offline, then don't send events which will update device watch
if (isOnline(id)) {
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
return []
}
@@ -981,39 +1015,38 @@ private handlePoll(body) {
def bulbs = getChildDevices()
for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
def device = bulbs.find { it.deviceNetworkId == "${app.id}/${bulb.key}" }
if (device) {
if (bulb.value.state?.reachable) {
if (state.bulbs[bulb.key]?.online == false) {
if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) {
// light just came back online, notify device watch
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
log.debug "$device is Online"
}
// Mark light as "online"
state.bulbs[bulb.key]?.unreachableSince = null
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
} else {
if (state.bulbs[bulb.key]?.unreachableSince == null) {
// Store the first time where device was reported as "unreachable"
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
} else if (state.bulbs[bulb.key]?.online) {
}
if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) {
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) {
log.warn "$device went Offline"
state.bulbs[bulb.key]?.online = false
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
}
}
log.warn "$device may not reachable by Hue bridge"
}
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
}
}
return []
@@ -1032,16 +1065,16 @@ def updateHandler() {
def hubVerification(bodytext) {
log.trace "Bridge sent back description.xml for verification"
def body = new XmlSlurper().parseText(bodytext)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) {
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a bridge that didn't exist"
}
}
def body = new XmlSlurper().parseText(bodytext)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) {
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a bridge that didn't exist"
}
}
}
def on(childDevice) {
@@ -1050,7 +1083,7 @@ def on(childDevice) {
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true])
return "Bulb is turning On"
return "Bulb is turning On"
}
def off(childDevice) {
@@ -1059,15 +1092,15 @@ def off(childDevice) {
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false])
return "Bulb is turning Off"
return "Bulb is turning Off"
}
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 1 - 254
def level
def level
if (percent == 1)
level = 1
else
@@ -1101,7 +1134,7 @@ def setSaturation(childDevice, percent) {
def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on?
@@ -1113,7 +1146,7 @@ def setHue(childDevice, percent) {
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on")
@@ -1122,14 +1155,14 @@ def setColorTemperature(childDevice, huesettings) {
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
def value = [:]
def hue = null
def sat = null
def xy = null
def value = [:]
def hue = null
def sat = null
def xy = null
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
if (huesettings.hue != null || huesettings.sat != null) {
@@ -1154,48 +1187,32 @@ def setColor(childDevice, huesettings) {
// value.xy = calculateXY(hex, model)
// Once groups, or scenes are introduced it might be a good idea to use unique models again
value.xy = calculateXY(hex)
}
}
*/
// Default behavior is to turn light on
value.on = true
// Default behavior is to turn light on
value.on = true
if (huesettings.level != null) {
if (huesettings.level <= 0)
value.on = false
else if (huesettings.level == 1)
value.bri = 1
else
if (huesettings.level != null) {
if (huesettings.level <= 0)
value.on = false
else if (huesettings.level == 1)
value.bri = 1
else
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
// Make sure to turn off light if requested
if (huesettings.switch == "off")
value.on = false
// Make sure to turn off light if requested
if (huesettings.switch == "off")
value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value)
put("lights/$id/state", value)
return "Setting color to $value"
}
def ping(childDevice) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) {
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
return "Bridge is Online"
} else {
return "Bridge is Offline"
}
} else if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1]
@@ -1246,30 +1263,30 @@ private getBridgeIdNumber(serialNumber) {
private getBridgeIP() {
def host = null
if (selectedHue) {
def d = getChildDevice(selectedHue)
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
if (bridge?.ip.contains("."))
host = "${bridge?.ip}:${bridge?.port}"
else
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
}
log.trace "Bridge: $selectedHue - Host: $host"
}
return host
def d = getChildDevice(selectedHue)
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
if (bridge?.ip.contains("."))
host = "${bridge?.ip}:${bridge?.port}"
else
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
}
log.trace "Bridge: $selectedHue - Host: $host"
}
return host
}
private Integer convertHexToInt(hex) {
@@ -1309,7 +1326,7 @@ private List getRealHubFirmwareVersions() {
* @param childDevice device to send event for
* @param setSwitch The new switch state, "on" or "off"
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
* that should generate "off" instead of level change
* that should generate "off" instead of level change
*/
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
@@ -1452,8 +1469,8 @@ private float[] calculateXY(colorStr, model = null) {
xy[0] = closestPoint.x;
xy[1] = closestPoint.y;
}
// xy[0] = PHHueHelper.precision(4, xy[0]);
// xy[1] = PHHueHelper.precision(4, xy[1]);
// xy[0] = PHHueHelper.precision(4, xy[0]);
// xy[1] = PHHueHelper.precision(4, xy[1]);
// TODO needed, assume it just sets number of decimals?
@@ -1471,7 +1488,7 @@ private float[] calculateXY(colorStr, model = null) {
*
* @param points the float array contain x and the y value. [x,y]
* @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut.
* If this value is empty the default gamut values are used.
* If this value is empty the default gamut values are used.
* @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned
*/
private String colorFromXY(points, model ) {
@@ -1785,3 +1802,4 @@ def hsvToHex(hue, sat, value = 100){
return "#$r1$g1$b1"
}

View File

@@ -289,12 +289,12 @@ def initializeLife360Connection() {
state.life360AccessToken = result.data.access_token
return true;
}
log.debug "Response=${result.data}"
log.info "Life360 initializeLife360Connection, response=${result.data}"
return false;
}
catch (e) {
log.debug e
log.error "Life360 initializeLife360Connection, error: $e"
return false;
}
@@ -656,7 +656,7 @@ def generateInitialEvent (member, childDevice) {
try { // we are going to just ignore any errors
log.debug "Generate Initial Event for New Device for Member = ${member.id}"
log.info "Life360 generateInitialEvent($member, $childDevice)"
def place = state.places.find{it.id==settings.place}
@@ -677,6 +677,8 @@ def generateInitialEvent (member, childDevice) {
// log.debug "Distance Away = ${distanceAway}"
boolean isPresent = (distanceAway <= placeRadius)
log.info "Life360 generateInitialEvent, member: ($memberLatitude, $memberLongitude), place: ($placeLatitude, $placeLongitude), radius: $placeRadius, dist: $distanceAway, present: $isPresent"
// log.debug "External Id=${app.id}:${member.id}"
@@ -718,7 +720,7 @@ def haversine(lat1, lon1, lat2, lon2) {
def placeEventHandler() {
log.debug "In placeEventHandler method."
log.info "Life360 placeEventHandler: params=$params, settings.place=$settings.place"
// the POST to this end-point will look like:
// POST http://test.com/webhook?circleId=XXXX&placeId=XXXX&userId=XXXX&direction=arrive
@@ -729,8 +731,6 @@ def placeEventHandler() {
def direction = params?.direction
def timestamp = params?.timestamp
log.debug "Life360 Event: Circle: ${circleId}, Place: ${placeId}, User: ${userId}, Direction: ${direction}"
if (placeId == settings.place) {
def presenceState = (direction=="in")
@@ -745,10 +745,10 @@ def placeEventHandler() {
if (deviceWrapper) {
deviceWrapper.generatePresenceEvent(presenceState)
log.debug "Event raised on child device: ${externalId}"
log.debug "Life360 event raised on child device: ${externalId}"
}
else {
log.debug "Couldn't find child device associated with inbound Life360 event."
log.warn "Life360 couldn't find child device associated with inbound Life360 event."
}
}

View File

@@ -242,8 +242,6 @@ def installed() {
} else {
initialize()
}
// Check for new devices and remove old ones every 3 hours
runEvery3Hours('updateDevices')
}
// called after settings are changed
@@ -271,9 +269,19 @@ private removeChildDevices(devices) {
def initialize() {
log.debug "initialize"
updateDevices()
// Check for new devices and remove old ones every 3 hours
runEvery5Minutes('updateDevices')
setupDeviceWatch()
}
// Misc
private setupDeviceWatch() {
def hub = location.hubs[0]
// Make sure that all child devices are enrolled in device watch
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
}
}
Map apiRequestHeaders() {
return ["Authorization": "Bearer ${state.lifxAccessToken}",
@@ -373,32 +381,46 @@ def updateDevices() {
selectors.add("${device.id}")
if (!childDevice) {
// log.info("Adding device ${device.id}: ${device.product}")
def data = [
label: device.label,
level: Math.round((device.brightness ?: 1) * 100),
switch: device.connected ? device.power : "unreachable",
colorTemperature: device.color.kelvin
]
if (device.product.capabilities.has_color) {
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
data["hue"] = device.color.hue / 3.6
data["saturation"] = device.color.saturation * 100
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data)
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, ["label": device.label, "completedSetup": true])
} else {
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, ["label": device.label, "completedSetup": true])
}
}
if (device.product.capabilities.has_color) {
childDevice.sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int))
childDevice.sendEvent(name: "hue", value: device.color.hue / 3.6)
childDevice.sendEvent(name: "saturation", value: device.color.saturation * 100)
}
childDevice.sendEvent(name: "label", value: device.label)
childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100))
childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100))
childDevice.sendEvent(name: "switch", value: device.power)
childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin)
childDevice.sendEvent(name: "model", value: device.product.name)
if (state.devices[device.id] == null) {
// State missing, add it and set it to opposite status as current status to provoke event below
state.devices[device.id] = [online: !device.connected]
}
if (!state.devices[device.id]?.online && device.connected) {
// Device came online after being offline
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is back Online"
} else if (state.devices[device.id]?.online && !device.connected) {
// Device went offline after being online
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.debug "$device went Offline"
}
state.devices[device.id] = [online: device.connected]
}
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
log.info("Deleting ${it.deviceNetworkId}")
state.devices[it.deviceNetworkId] = null
deleteChildDevice(it.deviceNetworkId)
}
runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block
}
def refreshDevices() {
log.info("Refreshing all devices...")
getChildDevices().each { device ->
device.refresh()
}
}

View File

@@ -24,6 +24,12 @@
* switches | switch | on, off | on, off
* motionSensors | motion | | active, inactive
* contactSensors | contact | | open, closed
* thermostat | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint
* | | setCoolingSetpoint(number) | coolingSetpoint, thermostatSetpoint
* | | off, heat, emergencyHeat | thermostatMode — ["emergency heat", "auto", "cool", "off", "heat"]
* | | cool, setThermostatMode | thermostatFanMode — ["auto", "on", "circulate"]
* | | fanOn, fanAuto, fanCirculate| thermostatOperatingState — ["cooling", "heating", "pending heat",
* | | setThermostatFanMode, auto | "fan only", "vent economizer", "pending cool", "idle"]
* presenceSensors | presence | | present, 'not present'
* temperatureSensors | temperature | | <numeric, F or C according to unit>
* accelerationSensors | acceleration | | active, inactive
@@ -58,6 +64,7 @@ preferences(oauthPage: "deviceAuthorization") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
@@ -509,7 +516,7 @@ def pollResponse(response, data) {
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
if (it.value.response.data.currentAvActivity == "-1") {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", displayed: false)
} else {
def currentActivity
def activityDTH = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}")
@@ -517,7 +524,7 @@ def pollResponse(response, data) {
currentActivity = activityDTH.device.displayName
else
currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", displayed: false)
}
}
} else {
@@ -569,7 +576,7 @@ def getActivityList() {
}
activities += [id: "off", name: "Activity OFF", type: "0"]
}
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", displayed: false)
}
}
}
@@ -936,7 +943,7 @@ def deleteHarmony() {
}
private getAllDevices() {
([] + switches + motionSensors + contactSensors + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
([] + switches + motionSensors + contactSensors + thermostats + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
}
private deviceItem(device) {