Compare commits

..

325 Commits

Author SHA1 Message Date
이현정
cf19c54c0f MSA-1188: test 2016-04-17 19:23:26 -05:00
Yaima
33ef75091b Merge pull request #783 from Yaima/master
Fixes broken htmlTile
2016-04-14 16:05:55 -07:00
Yaima Valdivia
1bcad614ec Fixes broken htmlTile 2016-04-14 16:03:58 -07:00
Luke Bredeson
3ab83350f3 Merge pull request #761 from lbredeso/hue-event-subscription-improvements
EX-3: Improve Hue (Connect) event subscriptions
2016-04-14 16:58:01 -05:00
Vinay Rao
d91fea89df Merge pull request #779 from SmartThingsCommunity/staging
Rolling down hotfix from staging to master
2016-04-13 23:28:57 -05:00
Vinay Rao
d28d27c4ed Merge pull request #778 from SmartThingsCommunity/production
Rolling down hotfix from production to staging
2016-04-13 22:34:01 -05:00
Vinay Rao
e7c1d88285 Merge pull request #773 from SmartThingsCommunity/fix-multi-thermostat-dth
Update tile-multiattribute-thermostat.groovy
2016-04-12 10:03:43 -07:00
Kyle LeNeau
74c334a0f7 Update tile-multiattribute-thermostat.groovy 2016-04-12 12:02:15 -05:00
Vinay Rao
6881f469f5 Merge pull request #768 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-04-12 Release
2016-04-12 09:27:46 -07:00
Dylan Bijnagte
be8c84306a Merge pull request #770 from Bijnagte/INTL-525
INTL-525 translation refresh
2016-04-11 15:57:46 -05:00
dylanbijnagte
a6105188ea INTL-525 translation refresh 2016-04-11 13:44:40 -05:00
Vinay Rao
22f218c072 Merge pull request #769 from SmartThingsCommunity/master
Adding the mobile ux test changes to the release train
2016-04-07 17:15:11 -07:00
Kyle LeNeau
7c000dc61a Merge pull request #629 from KyleLeneau/tile-coverage
Create an every element SmartApp equivalent for Device Tiles
2016-04-07 14:28:33 -07:00
Vinay Rao
8b543d399b Merge pull request #767 from SmartThingsCommunity/master
Rolling up master to staging
2016-04-07 13:55:16 -07:00
Vinay Rao
bb21b9a612 Merge pull request #766 from SmartThingsCommunity/staging
Rolling down hue hotfix to master
2016-04-07 13:40:44 -07:00
Vinay Rao
e366a2686f Merge pull request #765 from SmartThingsCommunity/production
Rolling down production hue hotfix to staging
2016-04-07 12:40:43 -07:00
Tyler Lange
820405a3ab Merge pull request #559 from macand/MSA-906-3
MSA-906: Fibaro Door/Window Sensor ZW5
2016-04-07 09:49:24 -07:00
Tyler Lange
69875becae Merge pull request #558 from macand/MSA-905-2
MSA-905: Fibaro Motion Sensor ZW5
2016-04-07 09:49:05 -07:00
Tyler Lange
5c90091e36 Merge pull request #557 from macand/MSA-904-1
MSA-904: Fibaro Flood Sensor ZW5
2016-04-07 09:48:39 -07:00
Vinay Rao
af19cee795 Merge pull request #764 from juano2310/production
Switched colors for hue bulb
2016-04-06 13:47:42 -07:00
juano2310
55ed08d5e7 Switched colors around 2016-04-06 16:34:13 -04:00
Juan Pablo Risso
2dec6f69c8 Merge pull request #762 from juano2310/production
CHANGE-479 - Restore Hue Bulb Color
2016-04-06 12:45:16 -04:00
juano2310
381fcfdd31 CHANGE-479 - Restore Hue Bulb Color 2016-04-06 11:59:24 -04:00
Kyle LeNeau
b23d7ccf2e Adding a SmartApp and stubs for all the device type tiles 2016-04-05 23:30:42 -07:00
Luke Bredeson
237d6a79e9 EX-3: Improve Hue (Connect) event subscriptions 2016-04-05 13:28:45 -05:00
Vinay Rao
fd29fe2b2a Merge pull request #758 from workingmonk/feature/new_button_dth
Changing the name for the DTH and the namespace
2016-04-04 17:07:28 -07:00
Vinay Rao
c259af4312 change name for button to generic name 2016-04-04 17:06:25 -07:00
Vinay Rao
82214e29eb Merge pull request #753 from SmartThingsCommunity/staging
Rolling down staging hotfixes to master
2016-04-01 13:51:12 -07:00
Vinay Rao
bf00284c74 Merge pull request #752 from SmartThingsCommunity/production
Rolling down prod hotfix to staging
2016-04-01 13:44:39 -07:00
Vinay Rao
8de5ed77f4 Merge pull request #751 from jeff-blaisdell/slack
Add Slack integration into build process.
2016-04-01 13:43:37 -07:00
Doug Sabers
4d31f8dbe8 Merge pull request #747 from sabersd/transUpdates
INTL-520 updating translations
2016-04-01 15:37:39 -05:00
Jeff Blaisdell
9d378ce9a1 Add Slack integration into build process. 2016-04-01 14:39:55 -05:00
sabersd
8abe4ac29f INTL-520 updating translations 2016-03-31 22:06:23 -05:00
Doug Sabers
104fa8d616 Merge pull request #745 from sabersd/apTrans
INTL-309 missing translations
2016-03-31 17:53:09 -05:00
sabersd
5b5e185ef0 INTL-309 missing translations 2016-03-31 17:52:13 -05:00
Vinay Rao
3a0c9c1298 Merge pull request #743 from mitchpond/smart-fob-forPR
Merging in non fingerprint DTH from community
2016-03-31 15:31:52 -07:00
Vinay Rao
51fb7fc7a9 Merge pull request #717 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-31 Release
2016-03-31 14:31:39 -07:00
Mitch Pond
4a096fc884 Iris Smart Fob - cleaned up and commented out fingerprint for submission 2016-03-31 16:36:42 +00:00
Doug Sabers
babc0206df Merge pull request #742 from sabersd/tilesWork
INTL-309 translating tiles
2016-03-31 10:02:43 -05:00
sabersd
d419fb8606 INTL-309 translating tiles 2016-03-31 09:28:25 -05:00
Lars Finander
70aae0d76c Merge pull request #720 from larsfinander/staging_DVCSMP_1667_Authentication_update_Philips_Hue
DVCSMP-1667 Authentication needs to be updated for Philips Hue
2016-03-30 16:37:16 -07:00
Lars Finander
1cd5c68e68 DVCSMP-1667 Authentication needs to be updated for Philips Hue
-Philips updaded their API and current ST implementation will stop
working when next Hue firmware is released without this change
2016-03-30 13:58:53 -07:00
tslagle13
9e427d4108 Merge pull request #718 from tslagle13/bug-fix-for-vacation-lighting-director
Make timeIntervalInput dynamic page
2016-03-29 17:20:14 -07:00
tslagle13
a8628b7343 Make timeIntervalInput dynamic page
Forgot to make timeIntervalInput a dynamic page so it can update view on selection.
2016-03-29 17:12:28 -07:00
Lars Finander
1736caebfe Merge pull request #716 from larsfinander/DVCSMP-1597-Hue-remove-temp-control
DVCSMP-1597 Bloom and Strip lights need temperature control removed
2016-03-29 14:08:45 -07:00
Lars Finander
0fa363fa1a Merge pull request #715 from larsfinander/DVCSMP-1672-hue-lux-icon-color
DVCSMP-1672 Phillips HUE: Mismatched on button  color for hue lux
2016-03-29 13:42:22 -07:00
Lars Finander
0c5840087b DVCSMP-1597 Philips HUE: Bloom and Strip lights need temperature control removed
-Added new device type for Hue lights that have color control but no temperature control (Bloom/Strip)
-Add missing event to setLevel
2016-03-29 11:26:25 -07:00
Lars Finander
a6ee53641f DVCSMP-1672 Phillips HUE: Mismatched in on button light color for hue lux
-Changed color from green to blue to match other Hue lights
2016-03-28 20:51:32 -07:00
Duncan McKee
c6818c8c2b Add new Ecolink Motion MSR to door/window retyping code 2016-03-28 13:29:51 -04:00
Vinay Rao
6ac174c2f3 Merge pull request #707 from SmartThingsCommunity/staging
Rolling down build changes to master from staging
2016-03-25 16:51:58 -07:00
Vinay Rao
eb8d5ed4c9 Merge pull request #706 from SmartThingsCommunity/production
Rolling down build changes to staging from production
2016-03-25 16:47:12 -07:00
Vinay Rao
7b5d618de8 Merge pull request #705 from jeff-blaisdell/fix-production
Update public repo to new build setup.
2016-03-25 16:45:47 -07:00
Jeff Blaisdell
c024e09fb8 Update public repo to new build setup. 2016-03-25 18:33:15 -05:00
Amol Mundayoor
e5841fb3cb Merge pull request #702 from larsfinander/DVCSMP-1667-Update-Hue-user-creation
DVCSMP-1667 Authentication needs to be updated for Philips Hue
2016-03-25 11:28:19 -07:00
Lars Finander
805b870447 DVCSMP-1667 Authentication needs to be updated for Philips Hue 2016-03-25 10:12:48 -07:00
Vinay Rao
fe92f7ad19 Merge pull request #698 from SmartThingsCommunity/master
Rolling up master to staging
2016-03-24 13:25:36 -07:00
Vinay Rao
10245315ee Merge pull request #697 from SmartThingsCommunity/staging
Rolling down staging commits to master
2016-03-24 13:22:33 -07:00
Vinay Rao
0b239d4686 Merge pull request #696 from SmartThingsCommunity/revert-668-staging_remove_hue
Revert "Revert "DVCSMP-1615 & DEVC-372""
2016-03-24 13:19:48 -07:00
Vinay Rao
9374290d64 Revert "Revert "DVCSMP-1615 & DEVC-372"" 2016-03-24 13:12:23 -07:00
Vinay Rao
ffcacb9da5 Merge pull request #664 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-25 Release
2016-03-24 13:03:24 -07:00
Vinay Rao
c45129170a Merge pull request #668 from larsfinander/staging_remove_hue
Revert "DVCSMP-1615 & DEVC-372"
2016-03-24 12:56:35 -07:00
Lars Finander
53406ada8e Merge pull request #690 from larsfinander/DVCSMP-1615-Improve-Hue-error-handling
DVCSMP-1615 Hue setColor throws exceptions when missing parameters
2016-03-23 18:57:32 -07:00
Lars Finander
ffd0dd1545 DVCSMP-1615 Hue setColor throws exceptions when missing parameters
-DVCSMP-1637 Color picker set the light on but status not updated on the detail page
-DVCSMP-1638 Temperature control selection set the light on but status not updated on the detail page
-Added a lot of error checking for input parameters
-Fixed some data parsing exceptions
2016-03-23 16:10:11 -07:00
Vinay Rao
5c1236a21a Merge pull request #693 from SmartThingsCommunity/staging
[DVCSMP-1657] Syncing down changes from staging to master
2016-03-23 15:24:13 -07:00
Vinay Rao
0911651f71 Merge pull request #692 from workingmonk/bug/hotfix_multi
[DVCSMP-1657] Bug/hotfix multi
2016-03-23 15:19:18 -07:00
Vinay Rao
9cc92b1987 fixing issue with list of list of maps with multi parsing
adding return type to methods
2016-03-23 15:13:33 -07:00
Vinay Rao
1e27dc1824 Merge pull request #674 from workingmonk/feature/zigbee_library_refactor
[DPROT-20] ZigBee Refactor
2016-03-23 11:44:42 -07:00
Tom Manley
4bf3679942 Merge pull request #376 from tpmanley/feature/zigbee_refactor
[DPROT-20] Feature/zigbee refactor
2016-03-23 09:51:37 -05:00
Tom Manley
c714720578 Update zigbee-lock, zigbee-dimmer and zigbee-dimmer-power to use ZB lib API
https://smartthings.atlassian.net/browse/DPROT-20
2016-03-23 09:51:02 -05:00
Vinay Rao
281fc939ac refactoring code to get it inline with the new zigbee apis 2016-03-23 00:27:23 -07:00
tslagle13
03c2dec425 Merge pull request #667 from tslagle13/fixes-to-vacation-lighting-director
Update vacation-lighting-director.groovy
2016-03-22 17:38:42 -07:00
Lars Finander
633bef2ac5 Revert "DVCSMP-1615 & DEVC-372"
This reverts commit 6fbef3b297.
(temporary for staging, MArch 25 deploy)
2016-03-22 14:29:32 -07:00
tslagle13
38d0ca6170 Update vacation-lighting-director.groovy
* Moved scheduling over to Cron and added time as a trigger. 
 * Cleaned up formatting and some typos.
 * Updated license.
 * Made people option optional
 * Added sttement to unschedule on mode change if people option is not selected
2016-03-22 13:00:49 -07:00
Duncan McKee
836dd608c6 Merge pull request #539 from SmartThingsCommunity/DEVFRWK-78
Fix First Alert smoke alarm check-in handling
2016-03-21 22:33:07 -04:00
Juan Pablo Risso
43e4db28eb Merge pull request #660 from juano2310/hue_discover
Added hubVerification()
2016-03-21 16:21:33 -04:00
juano2310
df421a51ac Added hubVerification()
If the hub exist as a thing parsing the response from description.xml
was lost. Now it is sent back to the parent were the device can be
marked as verified if the modelName starts with "Philips hue bridge"
2016-03-21 14:38:24 -04:00
Duncan McKee
3affdd21fc Merge pull request #540 from SmartThingsCommunity/DVCSMP-1438
Z-Wave Motion Sensor: fix MissingMethodException on NotificationReport
2016-03-21 12:21:30 -04:00
Vinay Rao
a8357e7644 Merge pull request #652 from SmartThingsCommunity/master
Rolling up changes from master to staging
2016-03-18 13:38:31 -07:00
Vinay Rao
42865abc55 Merge pull request #651 from SmartThingsCommunity/staging
Rolling down changes in staging to master
2016-03-18 13:11:08 -07:00
Vinay Rao
024a6cb698 Merge pull request #632 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-17 Release
2016-03-18 10:43:22 -07:00
Lars Finander
dc1c78391e Merge pull request #650 from SmartThingsCommunity/MSA-963-2
MSA-963: Vinli Home Connect
2016-03-17 11:24:02 -07:00
Daniel
e279172383 Modifying 'Vinli Home Connect' 2016-03-17 12:49:40 -05:00
Daniel
0deb26810d MSA-963: Vinli Home Connect allows users to control their Smartthings Devices with their Vinli connect vehicles. The Vinli device is an OBD dongle that can report when it leaves or enters geofences. A user can, for instance, set their doors to lock and lights to turn off when they leave proximity to their home. 2016-03-17 12:18:14 -05:00
Tom Manley
d4fb75cc47 Merge pull request #649 from tpmanley/bugfix/multi_configure
multi: Fix occasional error with threeAxis attribute reporting
2016-03-17 12:06:52 -05:00
Nowak
8a5f0af0e2 added missing capability 2016-03-17 16:02:14 +01:00
Tom Manley
2276748a91 multi: Fix occasional error with threeAxis attribute reporting
Resolves:
     https://smartthings.atlassian.net/browse/DVCSMP-1623
2016-03-16 17:01:42 -05:00
static null
8d423e7c4b Merge pull request #648 from staticnull/INTL-414
INTL-414 Add device name translations to device properties files
2016-03-16 13:50:43 -05:00
staticnull
0dfbddee38 INTL-414 Add device name translations to device properties files 2016-03-16 13:27:55 -05:00
Dylan Bijnagte
40f88fa436 Merge pull request #647 from Bijnagte/INTL-353-fixes
INTL-353 revert non i18n changes
2016-03-16 11:31:19 -05:00
Dylan Bijnagte
c3c8bafef4 Merge pull request #644 from Bijnagte/INTL-289-fixes
INTL-289 revert white space and fingerprint removal changes
2016-03-16 11:06:35 -05:00
Dylan Bijnagte
1b9f758bd6 Merge pull request #646 from Bijnagte/INTL-290-fixes
INTL-290 revert non i18n changes
2016-03-16 10:17:24 -05:00
Dylan Bijnagte
ef1b04c08a Merge pull request #645 from Bijnagte/INTL-292-fixes
INTL-292 undo non i18n changes
2016-03-16 10:15:40 -05:00
dylanbijnagte
9cece36d69 INTL-353 revert non i18n changes 2016-03-16 09:21:24 -05:00
dylanbijnagte
be220e02b2 INTL-290 revert non i18n changes 2016-03-16 09:12:06 -05:00
dylanbijnagte
131cc7b016 INTL-292 undo non i18n changes 2016-03-16 08:23:22 -05:00
dylanbijnagte
dd1e76e95a INTL-289 revert white space and fingerprint removal changes 2016-03-16 08:12:02 -05:00
Amol Mundayoor
8aff9e78f6 Merge pull request #641 from juano2310/Hue_update
DVCSMP-1615 & DEVC-372
2016-03-15 17:04:43 -07:00
juano2310
6fbef3b297 DVCSMP-1615 & DEVC-372
DVCSMP-1615  - Fix exception if Hue, Saturation or Hex is sent to
setColor

DEVC-372 - Improves readability of Activity Feed
2016-03-15 16:19:53 -04:00
Yaima
0b5779528c Merge pull request #639 from Yaima/master
Removed log.info
2016-03-14 14:20:37 -07:00
Yaima Valdivia
63d25528ae Removed log.info 2016-03-14 14:20:09 -07:00
Luke Bredeson
d8fe639a51 Merge pull request #625 from lbredeso/wemo-missing-dni-method
EX-45: Wemo (Connect) device addition fails on update for old devices
2016-03-14 16:14:36 -05:00
Yaima
92fea16beb Merge pull request #638 from Yaima/master
Ecobee - mode/ fan mode
2016-03-14 12:30:42 -07:00
Yaima Valdivia
25ae1306c4 Ecobee - mode/ fan mode 2016-03-14 12:30:03 -07:00
Yaima
321389aee3 Merge pull request #637 from Yaima/master
Ecobee - set mode/ fan from smart app
2016-03-14 12:28:03 -07:00
Yaima Valdivia
106f09445b Ecobee - set mode/ fan from smart app
switchToFanMode(value)
switchToMode(value)
2016-03-14 12:20:36 -07:00
tslagle13
410e9f40cc Merge pull request #633 from tslagle13/tts-fixes
Fix text-to-speech feature
2016-03-14 11:08:45 -07:00
Rob Zienert
6f173981e4 Merge pull request #635 from robzienert/branch-name-improvement
MISC: specifying dev shards when master branch is being deployed
2016-03-14 11:17:10 -05:00
Rob Zienert
a94d8f2378 specifying dev shards when master branch is being deployed 2016-03-14 11:04:24 -05:00
tslagle13
1578c48440 Fix text-to-speech feature
Refactor case statement so custom message works. Remove comment from input selection.
2016-03-13 17:04:15 -07:00
Vinay Rao
2126d85f6e Merge pull request #631 from workingmonk/stage1_v1_multi_garage
[DVCSMP-1107] Prepping up v1 multi for garage door functionality
2016-03-11 15:03:03 -08:00
Yaima
9218b40e25 Merge pull request #624 from Yaima/master
Fixed Ecobee polling
2016-03-11 13:02:15 -08:00
Dylan Bijnagte
7c326ec7c3 Merge pull request #630 from Bijnagte/update-korean-dth
update korean translations
2016-03-11 14:45:20 -06:00
dylanbijnagte
74598ec943 update korean translations 2016-03-11 14:05:39 -06:00
Dylan Bijnagte
96a82797de Merge pull request #587 from twack/i18n-smartsense-moisture-sensor
INTL-292 i18n alignment for smartsense-moisture-sensor
2016-03-11 11:36:38 -06:00
Dylan Bijnagte
c8e4eef66b Merge pull request #588 from twack/i18n-smartsense-motion-sensor
INTL-289 i18n alignment for smartsense-motion-sensor
2016-03-11 11:32:50 -06:00
Dylan Bijnagte
5effd158fc Merge pull request #591 from twack/i18n-mobile-presence
INTL-387 i18n alignment for mobile-presense
2016-03-11 11:30:37 -06:00
Dylan Bijnagte
3a4b4d6345 Merge pull request #592 from twack/i18n-arrival-sensor-ha
INTL-359 Create i18n for arrival-sensor-ha
2016-03-11 11:29:58 -06:00
Dylan Bijnagte
27808c3996 Merge pull request #593 from twack/i18n-smartpower-outlet
INTL-353 create i18n korean translation for smartpower-outlet
2016-03-11 11:28:48 -06:00
Dylan Bijnagte
bd0d636b8c Merge pull request #589 from twack/i18n-smartsense-multi-sensor
INTL-290 i18n alignment for smartsense-multi-sensor
2016-03-11 11:27:40 -06:00
Vinay Rao
7526d2b445 Merge pull request #628 from SmartThingsCommunity/master
Rolling up master to staging
2016-03-10 18:35:42 -08:00
Vinay Rao
caf761c015 Merging changes from staging to master 2016-03-10 18:11:54 -08:00
Vinay Rao
62a965d90b Merge pull request #599 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-10 Release
2016-03-10 16:26:26 -08:00
Vinay Rao
fb9f1dee47 Merge pull request #626 from munds/hue-hotfix
Hue hotfix for color temperature and overall stability
2016-03-10 15:04:01 -08:00
Amol Mundayoor
3a433d3865 Hue hotfix for color temperature and overall stability 2016-03-10 14:21:49 -08:00
Luke Bredeson
fbb248dc31 EX-45: Wemo (Connect) device addition fails on update for old devices that lack MAC in device data 2016-03-10 13:58:44 -06:00
Yaima Valdivia
a53e506538 Fixed Ecobee polling
https://smartthings.atlassian.net/browse/DVCSMP-1511
Ecobee was polling once per device, creating multiple API calls. Now it
is polling once at the smartapp level every 5 minutes and sending the
response to each child device.
2016-03-10 11:52:47 -08:00
Matt Pennig
c15a09a077 Merge pull request #606 from SmartThingsCommunity/update-simulated-thermostat
Update simulated thermostat with new functionality and brand colors
2016-03-09 18:33:25 -06:00
Amol Mundayoor
2e4036d694 Merge pull request #610 from SmartThingsCommunity/MSA-941-1
MSA-941: Remove color temperature from Hue Lux.
2016-03-09 16:31:26 -08:00
Amol
49b6fb02df MSA-941: This is a device type change that removes the color temperature slider for Hue Lux bulb. Additionally, the refresh tile has been modified to not be for ants. It's a regular 2x2 tile (with scale: 2). 2016-03-09 17:50:24 -06:00
Nowak
e2ab965e89 updated initial sensor status (no motion) 2016-03-09 15:32:22 +01:00
Nowak
3824ccb5e1 updated initial sensor status (dry) 2016-03-09 15:31:52 +01:00
Vinay Rao
089ab00ab7 Merge pull request #581 from workingmonk/bug/ge-link-config
[DVCSMP-1588] changes to account for bad configure method for ge link.
2016-03-08 20:51:37 -08:00
Vinay Rao
a7b2e6a6cd changes to account for bad configure method for ge link. to account for users with bad config, hitting refresh should solve it for them.
updating parse
deleting unused code
updating the config method
adding logic to refresh after the dimming is complete for any rate specified by the user
multi tiles for ge link
2016-03-08 20:19:26 -08:00
Jason Botello
361eca6b15 Merge pull request #370 from SmartThingsCommunity/MSA-747-10
MSA-747: BeaconThings
2016-03-08 13:38:30 -08:00
Jason Botello
56d991b8d2 Merge pull request #431 from SmartThingsCommunity/MSA-794-2
MSA-794: Simple Control
2016-03-08 13:38:09 -08:00
Matt Pennig
20e112f4f8 Update sim. thermostat with new functionality and brand colors 2016-03-08 11:20:57 -06:00
Vinay Rao
e83d08cf2f Merge pull request #597 from workingmonk/bug/cree_bulb
[DVCSMP-1442] Cree bulb does not properly reflect dimming value
2016-03-07 20:17:53 -08:00
Vinay Rao
55905a10da issue with CREE Bulb not reporting back state
updating copyright and spacing
2016-03-07 19:13:19 -08:00
Juan Pablo Risso
546ee007f1 Merge pull request #561 from juano2310/hue-PROB-528
PROB-528 - Commented singleInstance: true
2016-03-07 17:27:37 -08:00
Juan Pablo Risso
21ae20302c Merge pull request #584 from juano2310/Hue_ColorTemp
DVCSMP-1565 & DVCSMP-1548
2016-03-07 17:27:18 -08:00
juano2310
9880ced851 Fix wrong DT for Hue bulb 2016-03-07 17:05:06 -08:00
twack
f593f08c5e create i18n korean translation for smartpower-outlet 2016-03-06 09:53:00 -08:00
twack
2d7300d9e5 create i18n and align arrival-sensor-ha SA file 2016-03-06 09:23:42 -08:00
twack
f31c3bf5ee i18n alignment for mobile-presense 2016-03-06 05:35:09 -08:00
twack
0799722bdb i18n alignment for smartsense-multi-sensor 2016-03-05 23:45:30 -08:00
twack
7fdb99524e i18n alignment for smartsense-motion-sensor 2016-03-05 23:15:21 -08:00
twack
04941dfa21 i18n groovy alignment for smartsense-moisture-sensor 2016-03-05 22:23:08 -08:00
juano2310
fb99a81704 fixed rich-control 2016-03-04 17:15:49 -05:00
juano2310
6bda59c340 DVCSMP-1565 & DVCSMP-1548
DVCSMP-1565 Color for light is not adjusted.
DVCSMP-1548 Color temperature
2016-03-04 17:08:24 -05:00
Vinay Rao
c1422438ac Merge pull request #576 from workingmonk/bug/refresh_tile
fix device.refresh for refresh tiles
2016-03-03 14:18:13 -08:00
Yaima
8ed23f4c7e Merge pull request #577 from Yaima/master
Including unknown temperature values as part of the response for sensors
2016-03-02 14:24:04 -08:00
Yaima Valdivia
e7e6ea7d56 Including unknown temperature values as part of the response for sensors
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-03-02 14:20:44 -08:00
Vinay Rao
12896f4095 device.refresh change for tile 2016-03-01 19:51:27 -08:00
Vinay Rao
ab4e8a892a Merge pull request #572 from workingmonk/bug/battery_values
[DVCSMP-1255] Fixing issue with weird battery values
2016-03-01 17:01:27 -08:00
Vinay Rao
e076818573 Merge pull request #573 from workingmonk/deprecate_DTH
[DVCSMP-1463] Deprecating copied DTH
2016-03-01 16:57:50 -08:00
Vinay Rao
cd8bbca5ee removing the fingerprints from the additional copy of DTH 2016-03-01 12:46:16 -08:00
Vinay Rao
2d060bddfc fixing issue with weird battery values 2016-03-01 12:17:50 -08:00
Vinay Rao
8f25ff4434 Merge pull request #570 from SmartThingsCommunity/master
Rolling up changes to staging from master
2016-03-01 12:01:26 -08:00
Dylan Bijnagte
4da9730319 Merge pull request #550 from Bijnagte/device-translations
adding korean translations for DTHs
2016-03-01 13:57:05 -06:00
Donald C. Kirker
25db4f5235 Merge pull request #571 from SmartThingsCommunity/DVCSMP-1440
Remove 3axis tile definition from 1st gen multi.
2016-03-01 10:58:09 -08:00
Donald Kirker
eae2a9ca08 Remove 3axis tile definition from 1st gen multi. 2016-03-01 10:53:42 -08:00
Yaima
2dd2d7cba4 Merge pull request #569 from Yaima/master
Ecobee multiple sensors fix
2016-02-29 14:50:45 -08:00
Yaima Valdivia
f5708bca8b Copyright - Ecobee Sensor 2016-02-29 14:49:50 -08:00
Yaima Valdivia
a9da6d130a Changed copyright information for Ecobee Sensor
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:49:02 -08:00
Yaima Valdivia
3a2c6f86be Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# By Lars Finander
# Via Lars Finander
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
  DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
2016-02-29 14:39:36 -08:00
Yaima Valdivia
71d2b89a37 Ecobee multiple sensors fix
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:15:56 -08:00
Lars Finander
b131ba1507 Merge pull request #568 from larsfinander/hueConnectFix
DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyExcep…
2016-02-29 13:48:07 -08:00
Lars Finander
f04a9e3f7a DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
-Only parse messages with JSON if they are from Hue bridge
2016-02-29 13:41:58 -08:00
bflorian
6a905e4380 Merge pull request #567 from bflorian/DVCSMP-1531-hue-command-types
DVCSMP-1531 switch hue command methods to void
2016-02-29 14:37:16 -05:00
bflorian
442f16680d DVCSMP-1531 switch hue command methods to void 2016-02-29 14:11:51 -05:00
Nowak
1af43681a5 updated tiles 2016-02-29 15:10:26 +01:00
Nowak
b211b298c0 updated tiles 2016-02-29 15:10:02 +01:00
Yaima
1c68099b52 Merge pull request #562 from Yaima/master
Fix fan mode notifications
2016-02-26 12:54:54 -08:00
Yaima
cc9321ca9f Fix fan mode notifications
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-26 12:39:32 -08:00
tslagle13
da029000c9 Remove unnecessary inputs 2016-02-26 12:24:11 -08:00
Juan Pablo Risso
600a9a2ca1 PROB-528 - Commented singleInstance: true
This will allow any one with more than one Hue Bridge to install an instance for each Bridge.
2016-02-26 14:45:12 -05:00
Vinay Rao
515b268374 Merge pull request #549 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-02-25 Release
2016-02-26 10:51:42 -08:00
Vinay Rao
919c9b88ee Merge pull request #555 from workingmonk/lock_fingerprinting_form
[DEVTOOLS-506] Fixing lock fingerprint causing Fingerprint Tool issue
2016-02-26 09:25:32 -08:00
Nowak
23a76fa72b MSA-906: Device Handlers for Fibaro Door/Window Sensor ZW5 (with and without DS18B20 connected) 2016-02-26 13:18:11 +01:00
Nowak
a46f09a84a MSA-905: Device Handler for Fibaro Motion Sensor ZW5 2016-02-26 13:14:39 +01:00
Nowak
aff8dec3ce MSA-904: Device Handler for Fibaro Flood Sensor ZW5 2016-02-26 13:12:26 +01:00
Vinay Rao
2438942071 Fixing lock fingerprint causing Fingerprint Tool issue 2016-02-25 20:07:05 -08:00
Jeff Blaisdell
0d9bd98cfa Merge pull request #554 from jeff-blaisdell/devtools-483-2
DEVTOOLS-483: Add circle deployments for SmartThingsPublic.
2016-02-25 14:44:39 -06:00
Jeff Blaisdell
9e33f190c3 DEVTOOLS-483: Add circle deployments for SmartThingsPublic. 2016-02-25 14:43:55 -06:00
Duncan McKee
c959691536 Merge pull request #532 from SmartThingsCommunity/DEVC-246-16
DEVC-246: Enerwave motion doesn't always get the associationSet that the hub sends on join
2016-02-25 15:32:25 -05:00
Jeff Blaisdell
2cb687dca9 Merge pull request #552 from jeff-blaisdell/devtools-483
DEVTOOLS-483: Create CircleCI job for SmartThingsPublic
2016-02-25 13:42:32 -06:00
Lars Finander
70ec8a28f8 Merge pull request #553 from larsfinander/boseNpeFix
DVCSMP-1521 NPE in Bose (Connect)
2016-02-25 11:17:56 -08:00
Lars Finander
e255316474 DVCSMP-1521 NPE in Bose (Connect)
-NPE would occur if parsed LAN message is missing content-type header
2016-02-25 11:09:31 -08:00
Jeff Blaisdell
934449c326 DEVTOOLS-483: Create CircleCI job for SmartThingsPublic 2016-02-25 12:39:46 -06:00
Juan Pablo Risso
9e37a37991 Merge pull request #551 from juano2310/Harmony_hotfix_namingConflict
callbackUrl naming conflict
2016-02-25 12:09:31 -05:00
Juan Pablo Risso
050da829d3 callbackUrl naming conflict 2016-02-25 10:02:06 -05:00
Lars Finander
f616dcbdf6 Merge pull request #542 from larsfinander/lifxExceptionFix
DVCSMP-1505 LIFX setColor() throws exception
2016-02-24 15:27:26 -08:00
Duncan McKee
8933510436 Send associationSet to Enerwave Motion on wake only when it isn't already working 2016-02-24 18:07:48 -05:00
Donald C. Kirker
3130e6ad27 Merge pull request #493 from SmartThingsCommunity/DVCSMP-1440
DVCSMP-1440 Remove three-axis values.
2016-02-24 14:06:49 -08:00
Donald Kirker
0c87601c64 DVCSMP-1440 Remove three-axis values.
Remove 3axis tile definition as well.
2016-02-24 13:59:46 -08:00
Lars Finander
d1a5a8631f Merge pull request #548 from larsfinander/wemoNpeFix
DVCSMP-1517 Wemo (Connect) SmartApp throws NPE
2016-02-24 13:07:20 -08:00
dylanbijnagte
d86dcfd82f adding korean translations for DTHs 2016-02-24 14:34:52 -06:00
Lars Finander
2184c2b21a DVCSMP-1517 Wemo (Connect) SmartApp throws NPE
-Wemo (Connect) SmartApp throws NPE when trying to call subscribe on null object
2016-02-24 10:01:59 -08:00
Vinay Rao
16126abb2d Merge pull request #547 from workingmonk/remove_samsung
[DVCSMP-1480] removing old samsung integration
2016-02-23 15:55:36 -08:00
Vinay Rao
77525c3377 [DVCSMP-1480] removing old samsung integration 2016-02-23 15:42:41 -08:00
Vinay Rao
9b87d39fe8 Merge pull request #546 from SmartThingsCommunity/master
Rolling up changes from master to staging
2016-02-23 10:50:20 -08:00
Vinay Rao
c17b856e6b Merge pull request #545 from SmartThingsCommunity/staging
Syncing down changes from staging to master
2016-02-23 10:45:43 -08:00
Lars Finander
42b790ef10 DVCSMP-1505 LIFX setColor() throws exception
-Added empty return to command methods since sendEvent is used
-Added null check for hex values
2016-02-22 15:13:56 -08:00
Yaima
a4ebe87f4e Merge pull request #541 from Yaima/master
Added colors for degrees in Celsius - https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-22 14:59:31 -08:00
Yaima Valdivia
f84e21d83a Added colors for degrees in Celsius
Emergency heat  mapped to auxHeatOnly
2016-02-22 14:55:15 -08:00
Duncan McKee
e61be4ff9c DVCSMP-1438: Fix Z-Wave Motion handling of Notification Report 2016-02-22 14:55:58 -05:00
Duncan McKee
6123fbeea5 DEVFRWK-78 Fix First Alert smoke alarm check-in handling 2016-02-22 13:52:56 -05:00
Yaima
85175eb298 Merge pull request #529 from Yaima/master
Ecobee - Changed tiles order
2016-02-19 14:45:01 -08:00
Luke Bredeson
c75568bcf1 Merge pull request #518 from lbredeso/wemo-subscription-improvements
INSIDE-787: Improve Wemo (Connect) event subscriptions
2016-02-19 10:10:23 -06:00
Donald C. Kirker
a9c078c0cb DEVC-246: As noted in the Z-Wave Door/Window Sensor source code, "Enerwave motion doesn't always get the associationSet that the hub sends on join". It appears that sometimes when the Enerwave motion sensor joins it does not report events, which would be indicative of this. What can be done to resolve this? Is this a manufacturer firmware bug, or a bug with our stack? Enerwave claims that their DTH works "perfectly". 2016-02-19 05:41:34 -06:00
Tom Manley
edc98e4840 Merge pull request #497 from tpmanley/feature/outlet_fingerprint
outlet: Added fingerprint for new ST outlet
2016-02-18 19:25:39 -06:00
Vinay Rao
fb2c2cb2a7 Merge pull request #530 from workingmonk/iris_motion_contact
[DEVC-259] Iris motion and contact sensor fingerprints
2016-02-18 17:22:56 -08:00
Vinay Rao
62aeb0533d Iris motion and contact sensor fingeprints 2016-02-18 17:07:11 -08:00
Yaima Valdivia
fb6cbcc35e Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# Via Yaima
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
2016-02-18 15:50:54 -08:00
Yaima Valdivia
097584944e Ecobee - Changed tiles order 2016-02-18 15:48:30 -08:00
Yaima
01fae3dcd4 Merge pull request #525 from Yaima/master
Better exception handling for Ecobee
2016-02-18 15:13:34 -08:00
Yaima Valdivia
6c125fe80f Better exception handling of Ecobee
Refreshing only if status code 14 - Authentication token has expired.
Refresh your tokens.
2016-02-18 14:49:26 -08:00
Vinay Rao
a103d437c2 Merge pull request #523 from SmartThingsCommunity/staging
Deploy to production 2/18
2016-02-18 09:28:02 -08:00
Vinay Rao
ae705deba4 Merge pull request #524 from SmartThingsCommunity/plantlink-merge
MSA-884: This is a quick update to a previously published device hand…
2016-02-17 19:32:49 -08:00
Oso Technologies
69a6fc4f9e MSA-884: This is a quick update to a previously published device handler to accept the "update" packet that occurs when the zigbee pairs with the hub to stop an exception due to it not being in the map format that other packets are in. 2016-02-17 19:31:22 -08:00
Yaima
5728f08770 Merge pull request #522 from Yaima/master
Fixed Ecobee - HH errors
2016-02-17 13:28:40 -08:00
Yaima Valdivia
f073df0a57 Fixed Ecobee - HH errors 2016-02-17 13:28:10 -08:00
Yaima
2af0db4e89 Merge pull request #520 from Yaima/master
Ecobee fanMode available - https://smartthings.atlassian.net/browse/D…
2016-02-17 11:30:43 -08:00
Yaima Valdivia
24bfb7f20f Ecobee fanMode available - https://smartthings.atlassian.net/browse/DVCSMP-1501
https://smartthings.atlassian.net/browse/DVCSMP-1501
2016-02-17 11:15:26 -08:00
Vinay Rao
d82a387c68 Merge pull request #517 from SmartThingsCommunity/master
Rolling up changes to staging from master
2016-02-17 07:47:16 -08:00
Luke Bredeson
9263107f0e INSIDE-787: Improve Wemo (Connect) event subscriptions 2016-02-16 17:05:09 -06:00
Yaima
41b9d71e3d Merge pull request #516 from Yaima/master
Ecobee 3 - https://smartthings.atlassian.net/browse/DEVC-285
2016-02-16 14:52:55 -08:00
Duncan McKee
e60a9d1925 Merge pull request #487 from dantheman2865/MSA-866-2
MSA-866: Update SmartSense Moisture to include Temperature Measurement from WWA02AA
2016-02-16 17:48:29 -05:00
Yaima Valdivia
27b7c24536 Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# By Juan Pablo Risso (3) and others
# Via Kris Schaller (24) and others
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
  PROB-870 - Harmony fails to save credentials
  add missing translations
  add event translation
  Removed canInstallLabs()
  remove segmented style input to prevent iOS crash
  PROB-537 - Fix error in line 335
  MSA-68: Spruce Irrigation controller and soil moisture sensors.
  # This is a combination of 3 commits. # The first commit's message is: MSA-68: Spruce Irrigation controller and soil moisture sensors.
  DVCSMP-1480 Fixed ArrayIndexOutOfBoundsException
  Convert closure to method
  Revert "Convert closure to method"
  Closure was causing sandbox issues locally
  Bugfixes for codeReports
  Fix Homeseer Multi Instance encap parse PROB-398
  Merge pull request #135 from kwarodom/fibaroSmokeSensor
2016-02-16 14:12:07 -08:00
Yaima Valdivia
13d9137c9a Ecobee 3 - https://smartthings.atlassian.net/browse/DEVC-285
https://smartthings.atlassian.net/browse/DEVC-285
https://smartthings.atlassian.net/browse/DVCSMP-1431
2016-02-16 13:48:47 -08:00
Juan Pablo Risso
b672a0b810 Merge pull request #514 from juano2310/logitech_hotfix
PROB-870 - Harmony fails to save credentials
2016-02-16 15:47:26 -05:00
Dylan Bijnagte
2b6a6a47ce Merge pull request #375 from Bijnagte/notify-me-when-i18n-events
add event translation
2016-02-16 14:39:29 -06:00
Juan Pablo Risso
7d07b93694 PROB-870 - Harmony fails to save credentials
It seams like the user removed some activities on Harmony side without removing them from SmartThings. This is causing an issue when adding new activities. This fix checks if the activity still exists before creating a new device.
2016-02-16 14:38:37 -05:00
dylanbijnagte
512bd3adc4 add missing translations 2016-02-16 11:30:20 -06:00
Steve Vlaminck
7f707a9dbb Merge pull request #512 from vlaminck/gentleWakeUp-iOS-crash-fix
remove segmented style input to prevent iOS crash
2016-02-16 10:24:47 -06:00
dylanbijnagte
86e097ba0a add event translation 2016-02-16 10:05:46 -06:00
Juan Pablo Risso
89ec1f207f Merge pull request #513 from juano2310/hue_bu
Removed canInstallLabs()
2016-02-16 10:07:45 -05:00
Juan Pablo Risso
664af57708 Removed canInstallLabs() 2016-02-16 09:35:21 -05:00
vlaminck
d9aa1e378d remove segmented style input to prevent iOS crash 2016-02-15 21:39:06 -06:00
Vinay Rao
376779598a Prepping up v1 multi for garage door functionality 2016-02-15 13:01:12 -08:00
Juan Pablo Risso
ce26a2941e Merge pull request #506 from juano2310/hue_bu
PROB-537 - Fix error in line 335
2016-02-12 14:34:00 -05:00
Juan Pablo Risso
81fb356a21 PROB-537 - Fix error in line 335 2016-02-12 14:31:04 -05:00
Vinay Rao
bdd88deb99 Merge pull request #504 from SmartThingsCommunity/staging
Merging changes from staging to production
2016-02-11 22:40:38 -08:00
Vinay Rao
20d660e236 Merge pull request #503 from SmartThingsCommunity/master
Merging into staging from master
2016-02-11 22:39:12 -08:00
Vinay Rao
aadbcc2eee Merge pull request #501 from SmartThingsCommunity/staging
Rolling down changes from staging to master
2016-02-11 22:11:01 -08:00
Vinay Rao
4a49d4c15d Merge pull request #108 from natec007/MSA-68-1
MSA-68: Spruce Irrigation
2016-02-11 22:06:25 -08:00
Nathan Cauffman
0e3bd5aa74 MSA-68: Spruce Irrigation controller and soil moisture sensors.
Merge in bug fixes and renames to match with new repository layout.

Deleted spruce-controller.groovy from 'Spruce Irrigation'

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

Deleted spruce-scheduler.groovy from 'Spruce Irrigation'

Modifying 'Spruce Irrigation'

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

Modifying 'Spruce Irrigation'

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

Updated device types, start scheduler from device

Code review for ST publication

Review for ST submission

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

Moisture control adjsut

# This is the 2nd commit message:

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

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

Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1225
2016-01-29 14:41:12 -06:00
Kris Schaller
208f0d97df Merging staging into production 2016-01-25 13:12:05 -08:00
Kris Schaller
e444c8d020 Merging master into staging 2016-01-25 13:11:18 -08:00
Vinay Rao
9f75bbfbde Merge pull request #463 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-20 17:13:25 -08:00
Vinay Rao
996ac27ba2 Merge pull request #462 from SmartThingsCommunity/master
Merging changes into stage
2016-01-20 17:06:44 -08:00
Vinay Rao
16b87c5eda Merge pull request #456 from SmartThingsCommunity/staging
Merging changes into prod from staging
2016-01-19 16:10:44 -08:00
Vinay Rao
05ad96d583 Merge pull request #455 from SmartThingsCommunity/master
Merging changes into stage
2016-01-19 16:08:42 -08:00
Vinay Rao
0a6bb51974 Merge pull request #453 from SmartThingsCommunity/staging
Merging ZigBee RGBW into prod
2016-01-19 15:57:18 -08:00
Vinay Rao
9ab74c3ba6 Merge pull request #452 from SmartThingsCommunity/master
Merging ZigBee RGBW into staging
2016-01-19 15:56:38 -08:00
Tom Manley
8f08a0819c Merge pull request #449 from SmartThingsCommunity/staging
Merge staging into production
2016-01-19 14:25:26 -06:00
Tom Manley
d34d1d3615 Merge pull request #448 from SmartThingsCommunity/master
Merge master into staging
2016-01-19 13:46:27 -06:00
Tom Manley
5085d7f184 Merge pull request #433 from SmartThingsCommunity/staging
Merge staging into production
2016-01-13 12:56:38 -06:00
Juan Pablo Risso
dc927e0659 Merge pull request #434 from SmartThingsCommunity/master
Master -> Staging
2016-01-13 13:21:51 -05:00
Tom Manley
991637b1c1 Merge pull request #432 from SmartThingsCommunity/master
Merge from master into staging
2016-01-13 10:39:39 -06:00
Will Price
783a30fa5f MSA-794: We submitted this ages ago when the review process took the better part of a year. Resubmitting here with minor changes based your recent email requesting all OAuth apps be submitted, please expedite ASAP as we have a huge number of users using the OAuth app.
These apps integrate SmartThings with Simple Control for Audio Video control. They are in use by a great many users already and quite well tested.
2016-01-11 15:50:14 -06:00
Juan Pablo Risso
1372d4005a Merge pull request #427 from SmartThingsCommunity/master
Master -> Staging
2016-01-08 16:08:16 -05:00
Yaima
9439efd7b9 Merge pull request #421 from SmartThingsCommunity/master
Merging Ecobee and SmartSense code
2016-01-06 15:34:01 -08:00
Yaima
70b8a042a7 Merge pull request #415 from SmartThingsCommunity/staging
Merging to prod
2016-01-05 16:01:55 -08:00
Yaima
451b7a4923 Merge pull request #414 from SmartThingsCommunity/master
Merging to staging
2016-01-05 16:01:05 -08:00
Kris Schaller
22c810e9df Merging staging into production 2016-01-04 03:43:46 -08:00
Kris Schaller
cc80373b89 Merging master into staging 2016-01-04 03:43:30 -08:00
Kris Schaller
ea98194abf Merging staging into production 2015-12-28 16:21:37 -08:00
Kris Schaller
94f11c583f Merging master into staging 2015-12-28 16:21:17 -08:00
Kris Schaller
4a6e000cee Merging staging into production 2015-12-28 15:41:51 -08:00
Kris Schaller
c3cf4089f4 Merging master into staging 2015-12-28 15:41:29 -08:00
Kris Schaller
71f9f19465 Merging staging into production 2015-12-28 10:31:40 -08:00
Kris Schaller
f8317a6d2c Merging master into staging 2015-12-28 10:31:20 -08:00
Kris Schaller
2edda411e2 Merging staging into production 2015-12-21 12:55:20 -08:00
Kris Schaller
dd19b6e73f Merging master into staging 2015-12-21 12:55:03 -08:00
Kris Schaller
ac74c6126d Merging staging into production 2015-12-15 09:38:16 -08:00
Kris Schaller
80e02416f3 Merging master into staging 2015-12-15 09:38:02 -08:00
obycode
177c816348 MSA-747: BeaconThings is a simple app to let you integrate iBeacons into your SmartThings smart home. With BeaconThings, you can get more reliable, and more specific location information. Spread some beacons around your home, register them with BeaconThings, and it will tell SmartThings when you're nearby. For each BeaconThing your register, a device is added to SmartThings that can be used with SmartRules, or custom SmartApps, to trigger events.
This submission contains the SmartApp to which the iOS app connects with OAuth, and the device type which is created by the SmartApp for each beacon registered in the app.
2015-12-15 10:46:27 -06:00
Kris Schaller
653f0e28ca Merging staging into production 2015-12-14 17:25:47 -08:00
Kris Schaller
49dc1e96ba Merging master into staging 2015-12-14 17:25:34 -08:00
Kris Schaller
76fcca90d3 Merging staging into production 2015-12-14 17:09:08 -08:00
Kris Schaller
c197f4263e Merging master into staging 2015-12-14 17:08:51 -08:00
Kris Schaller
54f976229e Merging staging into production 2015-12-14 12:59:39 -08:00
Kris Schaller
3ba148d5d2 Merging master into staging 2015-12-14 12:59:25 -08:00
Kris Schaller
041733373b Merging staging into production 2015-12-10 15:09:04 -08:00
Kris Schaller
b67783a235 Merging master into staging 2015-12-10 15:08:28 -08:00
bflorian
b90e2a1982 Merge branch 'staging' into production 2015-12-05 10:26:47 -05:00
bflorian
ae444dfe09 Merge branch 'staging' of https://github.com/SmartThingsCommunity/SmartThingsPublic into staging 2015-12-05 10:26:25 -05:00
Donald C. Kirker
df55116ac6 Merge pull request #330 from mckeed/prob-398
PROB-398 Fix Homeseer Multi Instance encap parse
2015-12-04 12:38:39 -08:00
Vinay Rao
7d0b3ef796 Merge pull request #333 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-12-02 15:51:30 -08:00
Vinay Rao
c3d5f60250 Merge pull request #332 from SmartThingsCommunity/master
Merging changes into stage
2015-12-02 15:49:34 -08:00
Duncan McKee
f55ea8b2f8 Fix Homeseer Multi Instance encap parse PROB-398 2015-12-02 11:49:13 -06:00
Juan Pablo Risso
968c9cc647 Merge pull request #317 from SmartThingsCommunity/staging
Staging -> Production
2015-11-26 11:09:51 -05:00
Juan Pablo Risso
3e7ce67ea4 Merge pull request #316 from SmartThingsCommunity/master
Master -> Staging
2015-11-26 11:08:44 -05:00
Vinay Rao
479651a330 Merge pull request #296 from SmartThingsCommunity/staging
Merging changes into prod from staging
2015-11-18 14:36:36 -08:00
Vinay Rao
39b00c2a04 Merge pull request #295 from SmartThingsCommunity/master
Merging changes into stage
2015-11-18 14:04:44 -08:00
Vinay Rao
5a52e69911 Merge remote-tracking branch 'origin/staging' into production 2015-11-13 17:10:15 -08:00
Vinay Rao
7f4384cd85 Merge remote-tracking branch 'origin/master' into staging 2015-11-13 17:09:47 -08:00
Vinay Rao
42479a94b4 Merge remote-tracking branch 'origin/staging' into production 2015-11-12 15:30:24 -08:00
Juan Pablo Risso
57e668f1f2 Merge pull request #268 from SmartThingsCommunity/master
Master -> Staging
2015-11-09 16:31:21 -05:00
Warodom Khamphanchai
0321a7f071 Merge pull request #135 from kwarodom/fibaroSmokeSensor
Fibaro Smoke Sensor: initial device type
2015-10-20 10:53:21 -07:00
Vinay Rao
855ed02ffa Merge remote-tracking branch 'origin/staging' into production 2015-10-15 11:55:46 -07:00
Vinay Rao
60fd008d4a Merge remote-tracking branch 'origin/master' into staging 2015-10-15 11:45:09 -07:00
Kris Schaller
df764d57c3 Merging staging into production 2015-10-13 16:15:40 -07:00
Kris Schaller
a96bb027c8 Merging master into staging 2015-10-13 16:15:28 -07:00
Vinay Rao
6b62f88bb7 Merge remote-tracking branch 'origin/master' into production 2015-10-06 14:13:30 -05:00
Vinay Rao
848bbdcf2b Merge remote-tracking branch 'origin/master' into staging 2015-10-06 13:58:55 -05:00
bflorian
12288accda Merge branch 'master' into staging 2015-10-02 07:21:38 -07:00
Juan Pablo Risso
3533943827 Merge pull request #161 from SmartThingsCommunity/master
Merge Master -> Staging
2015-10-01 11:02:20 -04:00
94 changed files with 10354 additions and 2211 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Eclipse files
/.settings
/.classpath
/.project
/eclipse/
/target-eclipse
# IntelliJ files
*.iws
*.iml
.idea
/out
*.ipr
# Gradle files
.gradletasknamecache
.gradle/
# Mac OS files
.DS_Store
# Build files
/build

105
build.gradle Normal file
View File

@@ -0,0 +1,105 @@
import java.nio.charset.StandardCharsets
import java.nio.file.Paths
import com.smartthings.deployment.slack.FileUpload
import com.smartthings.deployment.slack.Message
apply plugin: 'groovy'
apply plugin: 'smartthings-executable-deployment'
apply plugin: 'smartthings-slack'
buildscript {
dependencies {
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.7"
}
repositories {
mavenLocal()
jcenter()
maven {
credentials {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "http://artifactory.smartthings.com/libs-release-local"
}
}
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
}
slackSendMessage {
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
String token = project.hasProperty('slackToken') ? project.property('slackToken') : null
String webhookUrl = project.hasProperty('slackWebhookUrl') ? project.property('slackWebhookUrl') : null
String channel = project.hasProperty('slackChannel') ? project.property('slackChannel') : null
String drinks = 'https://dl.dropboxusercontent.com/s/m1z5mpd3c83lwev/minion_beer.jpeg?dl=0'
String wolverine = 'https://dl.dropboxusercontent.com/s/4lbjqzvm2v033u9/minion_wolverine.jpg?dl=0'
String beach = 'https://dl.dropboxusercontent.com/s/rqrfgxk53gfng69/minion_beach.png?dl=0'
String iconUrl
String color
String messageText
String username
switch (branch) {
case 'master':
username = 'Hickory'
iconUrl = wolverine
color = '#35D0F2'
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
break
case 'staging':
username = 'Dickory'
iconUrl = beach
color = '#FFDE20'
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
break
case 'production':
username = 'Dock'
iconUrl = drinks
color = '#FF1D23'
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
break
default:
username = 'Hickory'
iconUrl = wolverine
color = '#35D0F2'
messageText = "Began deployment of an _SmartThingsPublic[${branch}]_ branch. Have no idea what's going on."
}
List<String> archives = []
File rootDir = new File("${project.buildDir}/archives")
if (rootDir.exists()) {
// Create a list of archives which were deployed.
java.nio.file.Path rootPath = Paths.get(rootDir.absolutePath)
rootDir.eachFileRecurse { File file ->
if (file.name.endsWith('.tar.gz')) {
java.nio.file.Path archivePath = Paths.get(file.absolutePath)
archives.add(rootPath.relativize(archivePath).toString())
}
}
}
Date date = new Date()
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
// Required Task Arguments.
file = new FileUpload(
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
filename: "deployment-notes-${fileDate}.txt",
title: 'Deployment Notes',
channels: channel,
token: token,
color: color
)
message = new Message(
webhookUrl: webhookUrl,
username: username,
asUser: true,
iconUrl: iconUrl,
channel: channel,
fallback: 'Deployment Notification',
text: messageText
)
}

25
circle.yml Normal file
View File

@@ -0,0 +1,25 @@
machine:
java:
version:
oraclejdk8
dependencies:
override:
- echo "Nothing to do."
test:
override:
- echo "We don't have any tests :-("
deployment:
develop:
branch: master
commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
stage:
branch: staging
commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace

View File

@@ -0,0 +1,107 @@
/**
* BeaconThing
*
* Copyright 2015 obycode
*
* 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.
*
*/
import groovy.json.JsonSlurper
metadata {
definition (name: "BeaconThing", namespace: "com.obycode", author: "obycode") {
capability "Beacon"
capability "Presence Sensor"
capability "Sensor"
attribute "inRange", "json_object"
attribute "inRangeFriendly", "string"
command "setPresence", ["string"]
command "arrived", ["string"]
command "left", ["string"]
}
simulator {
status "present": "presence: 1"
status "not present": "presence: 0"
}
tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state("present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0")
state("not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
}
valueTile("inRange", "device.inRangeFriendly", inactiveLabel: true, height:1, width:3, decoration: "flat") {
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
}
main "presence"
details (["presence","inRange"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
}
def installed() {
sendEvent(name: "presence", value: "not present")
def emptyList = []
def json = new groovy.json.JsonBuilder(emptyList)
sendEvent(name:"inRange", value:json.toString())
}
def setPresence(status) {
log.debug "Status is $status"
sendEvent(name:"presence", value:status)
}
def arrived(id) {
log.debug "$id has arrived"
def theList = device.latestValue("inRange")
def inRangeList = new JsonSlurper().parseText(theList)
if (inRangeList.contains(id)) {
return
}
inRangeList += id
def json = new groovy.json.JsonBuilder(inRangeList)
log.debug "Now in range: ${json.toString()}"
sendEvent(name:"inRange", value:json.toString())
// Generate human friendly string for tile
def friendlyList = "Nearby: " + inRangeList.join(", ")
sendEvent(name:"inRangeFriendly", value:friendlyList)
if (inRangeList.size() == 1) {
setPresence("present")
}
}
def left(id) {
log.debug "$id has left"
def theList = device.latestValue("inRange")
def inRangeList = new JsonSlurper().parseText(theList)
inRangeList -= id
def json = new groovy.json.JsonBuilder(inRangeList)
log.debug "Now in range: ${json.toString()}"
sendEvent(name:"inRange", value:json.toString())
// Generate human friendly string for tile
def friendlyList = "Nearby: " + inRangeList.join(", ")
if (inRangeList.empty) {
setPresence("not present")
friendlyList = "No one is nearby"
}
sendEvent(name:"inRangeFriendly", value:friendlyList)
}

View File

@@ -0,0 +1,272 @@
/**
* Fibaro Door/Window Sensor ZW5
*
* Copyright 2016 Fibar Group S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Fibaro Door/Window Sensor ZW5 with Temperature", namespace: "fibargroup", author: "Fibar Group S.A.") {
capability "Battery"
capability "Contact Sensor"
capability "Sensor"
capability "Configuration"
capability "Tamper Alert"
capability "Temperature Measurement"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86", outClusters: ""
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86, 0x84", outClusters: ""//actual NIF
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
}
}
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
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"]
]
}
main "FGK"
details(["FGK","battery", "temperature"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def result = []
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
result = createEvent(
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
eventType: "ALERT",
name: "secureInclusion",
value: "failed",
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (cmd) {
log.debug "Parsed '${cmd}'"
zwaveEvent(cmd)
}
}
}
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
//crc16
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
def versions = [0x31: 5, 0x72: 2, 0x80: 1, 0x86: 1]
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from $cmd"
} else {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
//it is assumed that default notification events are used
//(parameter 20 was not changed before device's re-inclusion)
def map = [:]
if (cmd.notificationType == 6) {
switch (cmd.event) {
case 22:
map.name = "contact"
map.value = "open"
map.descriptionText = "${device.displayName}: is open"
break
case 23:
map.name = "contact"
map.value = "closed"
map.descriptionText = "${device.displayName}: is closed"
break
}
} else if (cmd.notificationType == 7) {
switch (cmd.event) {
case 0:
map.name = "tamper"
map.value = "inactive"
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
break
case 3:
map.name = "tamper"
map.value = "active"
map.descriptionText = "${device.displayName}: tamper alarm activated"
break
}
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
map.unit = "%"
map.displayed = true
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
def cmds = []
cmds << encap(zwave.batteryV1.batteryGet())
cmds << "delay 500"
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
cmds << "delay 1200"
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
[event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
log.debug "deviceIdData: ${cmd.deviceIdData}"
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
log.debug "deviceIdType: ${cmd.deviceIdType}"
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
String serialNumber = "h'"
cmd.deviceIdData.each{ data ->
serialNumber += "${String.format("%02X", data)}"
}
updateDataValue("serialNumber", serialNumber)
log.debug "${device.displayName} - serial number: ${serialNumber}"
}
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
log.debug "applicationVersion: ${cmd.applicationVersion}"
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
def map = [:]
if (cmd.sensorType == 1) {
// temperature
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
map.displayed = true
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
log.info "${device.displayName}: received command: $cmd - device has reset itself"
}
def configure() {
log.debug "Executing 'configure'"
def cmds = []
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
cmds += zwave.versionV1.versionGet()
cmds += zwave.batteryV1.batteryGet()
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
encapSequence(cmds, 500)
}
private secure(physicalgraph.zwave.Command cmd) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crc16(physicalgraph.zwave.Command cmd) {
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
"5601${cmd.format()}0000"
}
private encapSequence(commands, delay=200) {
delayBetween(commands.collect{ encap(it) }, delay)
}
private encap(physicalgraph.zwave.Command cmd) {
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
//todo: check if secure inclusion was successful
//if not do not send security-encapsulated command
if (secureClasses.find{ it == cmd.commandClassId }) {
secure(cmd)
} else {
crc16(cmd)
}
}

View File

@@ -0,0 +1,239 @@
/**
* Fibaro Door/Window Sensor ZW5
*
* Copyright 2016 Fibar Group S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Fibaro Door/Window Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
capability "Battery"
capability "Contact Sensor"
capability "Sensor"
capability "Configuration"
capability "Tamper Alert"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x86, 0x84", outClusters: ""
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
}
}
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
main "FGK"
details(["FGK","battery"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def result = []
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
result = createEvent(
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
eventType: "ALERT",
name: "secureInclusion",
value: "failed",
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (cmd) {
log.debug "Parsed '${cmd}'"
zwaveEvent(cmd)
}
}
}
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
//crc16
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
def versions = [0x72: 2, 0x80: 1, 0x86: 1]
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from $cmd"
} else {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
//it is assumed that default notification events are used
//(parameter 20 was not changed before device's re-inclusion)
def map = [:]
if (cmd.notificationType == 6) {
switch (cmd.event) {
case 22:
map.name = "contact"
map.value = "open"
map.descriptionText = "${device.displayName}: is open"
break
case 23:
map.name = "contact"
map.value = "closed"
map.descriptionText = "${device.displayName}: is closed"
break
}
} else if (cmd.notificationType == 7) {
switch (cmd.event) {
case 0:
map.name = "tamper"
map.value = "inactive"
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
break
case 3:
map.name = "tamper"
map.value = "active"
map.descriptionText = "${device.displayName}: tamper alarm activated"
break
}
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
map.unit = "%"
map.displayed = true
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
def cmds = []
cmds << encap(zwave.batteryV1.batteryGet())
cmds << "delay 1200"
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
[event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
log.debug "deviceIdData: ${cmd.deviceIdData}"
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
log.debug "deviceIdType: ${cmd.deviceIdType}"
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
String serialNumber = "h'"
cmd.deviceIdData.each{ data ->
serialNumber += "${String.format("%02X", data)}"
}
updateDataValue("serialNumber", serialNumber)
log.debug "${device.displayName} - serial number: ${serialNumber}"
}
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
log.debug "applicationVersion: ${cmd.applicationVersion}"
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
log.info "${device.displayName}: received command: $cmd - device has reset itself"
}
def configure() {
log.debug "Executing 'configure'"
def cmds = []
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
cmds += zwave.versionV1.versionGet()
cmds += zwave.batteryV1.batteryGet()
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
encapSequence(cmds, 500)
}
private secure(physicalgraph.zwave.Command cmd) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crc16(physicalgraph.zwave.Command cmd) {
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
"5601${cmd.format()}0000"
}
private encapSequence(commands, delay=200) {
delayBetween(commands.collect{ encap(it) }, delay)
}
private encap(physicalgraph.zwave.Command cmd) {
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
//todo: check if secure inclusion was successful
//if not do not send security-encapsulated command
if (secureClasses.find{ it == cmd.commandClassId }) {
secure(cmd)
} else {
crc16(cmd)
}
}

View File

@@ -0,0 +1,269 @@
/**
* Fibaro Flood Sensor ZW5
*
* Copyright 2016 Fibar Group S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Fibaro Flood Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
capability "Battery"
capability "Configuration"
capability "Sensor"
capability "Tamper Alert"
capability "Temperature Measurement"
capability "Water Sensor"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x9C, 0x31, 0x86", outClusters: ""
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"FGFS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.water", key:"PRIMARY_CONTROL") {
attributeState("dry", icon:"st.alarm.water.dry", backgroundColor:"#79b821")
attributeState("wet", icon:"st.alarm.water.wet", backgroundColor:"#ffa81e")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper 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", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main "FGFS"
details(["FGFS","battery", "temperature"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def result = []
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
result = createEvent(
descriptionText: "FGFS failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
eventType: "ALERT",
name: "secureInclusion",
value: "failed",
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72:2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (cmd) {
log.debug "Parsed '${cmd}'"
zwaveEvent(cmd)
}
}
}
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
//crc16
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
def versions = [0x31: 5, 0x72: 2, 0x80: 1]
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from $cmd"
} else {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
{
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
def cmds = []
cmds << encap(zwave.batteryV1.batteryGet())
cmds << "delay 500"
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
cmds << "delay 1200"
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
[event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
log.debug "deviceIdData: ${cmd.deviceIdData}"
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
log.debug "deviceIdType: ${cmd.deviceIdType}"
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
String serialNumber = "h'"
cmd.deviceIdData.each{ data ->
serialNumber += "${String.format("%02X", data)}"
}
updateDataValue("serialNumber", serialNumber)
log.debug "${device.displayName} - serial number: ${serialNumber}"
}
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
log.debug "applicationVersion: ${cmd.applicationVersion}"
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
map.unit = "%"
map.displayed = true
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def map = [:]
if (cmd.notificationType == 5) {
switch (cmd.event) {
case 2:
map.name = "water"
map.value = "wet"
map.descriptionText = "${device.displayName} is ${map.value}"
break
case 0:
map.name = "water"
map.value = "dry"
map.descriptionText = "${device.displayName} is ${map.value}"
break
}
} else if (cmd.notificationType == 7) {
switch (cmd.event) {
case 0:
map.name = "tamper"
map.value = "inactive"
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
break
case 3:
map.name = "tamper"
map.value = "active"
map.descriptionText = "${device.displayName}: tamper alarm activated"
break
}
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
def map = [:]
if (cmd.sensorType == 1) {
// temperature
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
map.displayed = true
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
log.info "${device.displayName}: received command: $cmd - device has reset itself"
}
def configure() {
log.debug "Executing 'configure'"
def cmds = []
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGFS' default wake up interval
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
cmds += zwave.versionV1.versionGet()
cmds += zwave.batteryV1.batteryGet()
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
encapSequence(cmds, 500)
}
private secure(physicalgraph.zwave.Command cmd) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crc16(physicalgraph.zwave.Command cmd) {
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
"5601${cmd.format()}0000"
}
private encapSequence(commands, delay=200) {
delayBetween(commands.collect{ encap(it) }, delay)
}
private encap(physicalgraph.zwave.Command cmd) {
def secureClasses = [0x20, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
//todo: check if secure inclusion was successful
//if not do not send security-encapsulated command
if (secureClasses.find{ it == cmd.commandClassId }) {
secure(cmd)
} else {
crc16(cmd)
}
}

View File

@@ -0,0 +1,281 @@
/**
* Fibaro Motion Sensor ZW5
*
* Copyright 2016 Fibar Group S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Fibaro Motion Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
capability "Battery"
capability "Configuration"
capability "Illuminance Measurement"
capability "Motion Sensor"
capability "Sensor"
capability "Tamper Alert"
capability "Temperature Measurement"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper 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("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
}
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
main "FGMS"
details(["FGMS","battery","temperature","illuminance"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def result = []
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
result = createEvent(
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
eventType: "ALERT",
name: "secureInclusion",
value: "failed",
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (cmd) {
log.debug "Parsed '${cmd}'"
zwaveEvent(cmd)
}
}
}
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
//crc16
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
def versions = [0x31: 5, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1]
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from $cmd"
} else {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
def map = [ displayed: true ]
switch (cmd.sensorType) {
case 1:
map.name = "temperature"
map.unit = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
break
case 3:
map.name = "illuminance"
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = "lux"
break
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def map = [:]
if (cmd.notificationType == 7) {
switch (cmd.event) {
case 0:
if (cmd.eventParameter[0] == 3) {
map.name = "tamper"
map.value = "inactive"
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
}
if (cmd.eventParameter[0] == 8) {
map.name = "motion"
map.value = "inactive"
map.descriptionText = "${device.displayName}: motion has stopped"
}
break
case 3:
map.name = "tamper"
map.value = "active"
map.descriptionText = "${device.displayName}: tamper alarm activated"
break
case 8:
map.name = "motion"
map.value = "active"
map.descriptionText = "${device.displayName}: motion detected"
break
}
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
map.unit = "%"
map.displayed = true
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
{
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
def cmds = []
cmds << encap(zwave.batteryV1.batteryGet())
cmds << "delay 500"
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
cmds << "delay 500"
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1))
cmds << "delay 1200"
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
[event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
log.debug "deviceIdData: ${cmd.deviceIdData}"
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
log.debug "deviceIdType: ${cmd.deviceIdType}"
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
String serialNumber = "h'"
cmd.deviceIdData.each{ data ->
serialNumber += "${String.format("%02X", data)}"
}
updateDataValue("serialNumber", serialNumber)
log.debug "${device.displayName} - serial number: ${serialNumber}"
}
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
log.debug "applicationVersion: ${cmd.applicationVersion}"
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
log.info "${device.displayName}: received command: $cmd - device has reset itself"
}
def configure() {
log.debug "Executing 'configure'"
def cmds = []
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds: 7200, nodeid: zwaveHubNodeId)//FGMS' default wake up interval
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
cmds += zwave.versionV1.versionGet()
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
cmds += zwave.batteryV1.batteryGet()
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1)
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
encapSequence(cmds, 500)
}
private secure(physicalgraph.zwave.Command cmd) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crc16(physicalgraph.zwave.Command cmd) {
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
"5601${cmd.format()}0000"
}
private encapSequence(commands, delay=200) {
delayBetween(commands.collect{ encap(it) }, delay)
}
private encap(physicalgraph.zwave.Command cmd) {
def secureClasses = [0x20, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
//todo: check if secure inclusion was successful
//if not do not send security-encapsulated command
if (secureClasses.find{ it == cmd.commandClassId }) {
secure(cmd)
} else {
crc16(cmd)
}
}

View File

@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
}
def parse(String description) {
log.debug description
def description_map = parseDescriptionAsMap(description)
def event_name = ""
def measurement_map = [
@@ -129,10 +129,7 @@ def parse(String description) {
zigbeedeviceid: device.zigbeeId,
created: new Date().time /1000 as int
]
if (description_map.cluster == "0000"){
/* version number, not used */
} else if (description_map.cluster == "0001"){
if (description_map.cluster == "0001"){
/* battery voltage in mV (device needs minimium 2.1v to run) */
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
event_name = "battery_status"
@@ -158,6 +155,10 @@ def parse(String description) {
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
if(nameAndValue.length == 2){
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}else{
map += []
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,128 @@
/**
* Simple Sync
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
metadata
{
definition (name: "Simple Sync", namespace: "roomieremote-agent", author: "Roomie Remote, Inc.")
{
capability "Media Controller"
}
// simulator metadata
simulator
{
}
// UI tile definitions
tiles
{
standardTile("mainTile", "device.status", width: 1, height: 1, icon: "st.Entertainment.entertainment11")
{
state "default", label: "Simple Sync", icon: "st.Home.home2", backgroundColor: "#55A7FF"
}
def detailTiles = ["mainTile"]
main "mainTile"
details(detailTiles)
}
}
def parse(String description)
{
def results = []
try
{
def msg = parseLanMessage(description)
if (msg.headers && msg.body)
{
switch (msg.headers["X-Roomie-Echo"])
{
case "getAllActivities":
handleGetAllActivitiesResponse(msg)
break
}
}
}
catch (Throwable t)
{
sendEvent(name: "parseError", value: "$t", description: description)
throw t
}
results
}
def handleGetAllActivitiesResponse(response)
{
def body = parseJson(response.body)
if (body.status == "success")
{
def json = new groovy.json.JsonBuilder()
def root = json activities: body.data
def data = json.toString()
sendEvent(name: "activities", value: data)
}
}
def getAllActivities(evt)
{
def host = getHostAddress(device.deviceNetworkId)
def action = new physicalgraph.device.HubAction(method: "GET",
path: "/api/v1/activities",
headers: [HOST: host, "X-Roomie-Echo": "getAllActivities"])
action
}
def startActivity(evt)
{
def uuid = evt
def host = getHostAddress(device.deviceNetworkId)
def activity = new groovy.json.JsonSlurper().parseText(device.currentValue('activities') ?: "{ 'activities' : [] }").activities.find { it.uuid == uuid }
def toggle = activity["toggle"]
def jsonMap = ["activity_uuid": uuid]
if (toggle != null)
{
jsonMap << ["toggle_state": toggle ? "on" : "off"]
}
def json = new groovy.json.JsonBuilder(jsonMap)
def jsonBody = json.toString()
def headers = [HOST: host, "Content-Type": "application/json"]
def action = new physicalgraph.device.HubAction(method: "POST",
path: "/api/v1/runactivity",
body: jsonBody,
headers: headers)
action
}
def getHostAddress(d)
{
def parts = d.split(":")
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}

View File

@@ -11,6 +11,17 @@
* for the specific language governing permissions and limitations under the License.
*
*/
/*
* Purpose: Arrival Sensor HA DTH File
*
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
*
* Change History:
* 1. 20160115 TW - Update/Edit to support i18n translations
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
*/
metadata {
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
@@ -32,7 +43,7 @@ metadata {
])
}
section {
input "checkInterval", "enum", title: "Presence timeout (minutes)",
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
}
}
@@ -82,7 +93,6 @@ def parse(String description) {
private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
}
@@ -94,6 +104,7 @@ private handleReportAttributeMessage(String description) {
* @param volts Battery voltage in .1V increments
*/
private handleBatteryEvent(volts) {
def descriptionText
if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
}
@@ -107,15 +118,17 @@ private handleBatteryEvent(volts) {
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
def value = batteryMap[volts]
if (value != null) {
def linkText = getLinkText(device)
descriptionText = '{{ linkText }} battery was {{ value }}'
def eventMap = [
name: 'battery',
value: pct,
descriptionText: "${linkText} battery was ${pct}%"
value: value,
descriptionText: descriptionText,
translatable: true
]
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
sendEvent(eventMap)
}
}
@@ -131,13 +144,19 @@ private handlePresenceEvent(present) {
stopTimer()
}
def linkText = getLinkText(device)
def descriptionText
if ( present )
descriptionText = "{{ linkText }} has arrived"
else
descriptionText = "{{ linkText }} has left"
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
descriptionText: descriptionText,
translatable: true
]
log.debug "Creating presence event: ${eventMap}"
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
sendEvent(eventMap)
}
@@ -158,4 +177,4 @@ def checkPresenceCallback() {
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}
}

View File

@@ -0,0 +1,36 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: Arrival Sensor HA i18n Translation File
#
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
#
# Change History:
# 1. 20160115 TW Initial release with informal Korean translation.
# 2. 20160121 TW Added def preference section titles.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 설정
'''Set Device Image'''.ko=기기 이미지 설정
'''Presence timeout (minutes)'''.ko=알람 유예 시간 설정 (분)
'''Tap to set'''.ko=눌러서 설정
'''Arrival Sensor'''.ko=도착알림 센서
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
# Events / Notifications
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
#==============================================================================

View File

@@ -1,7 +1,7 @@
/**
* Cree Bulb
*
* Copyright 2014 SmartThings
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -15,29 +15,29 @@
*/
metadata {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Refresh"
capability "Switch"
capability "Switch Level"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
}
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// 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"
}
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -62,18 +62,12 @@ metadata {
def parse(String description) {
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
def resultMap = zigbee.getEvent(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
sendEvent(resultMap)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
@@ -87,7 +81,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value)
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def refresh() {

View File

@@ -1,7 +1,5 @@
/**
* Ecobee Sensor
*
* Copyright 2015 Juan Risso
* 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:
@@ -12,6 +10,9 @@
* 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.
*
* Ecobee Sensor
*
* Author: SmartThings
*/
metadata {
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -19,18 +20,22 @@ metadata {
capability "Temperature Measurement"
capability "Motion Sensor"
capability "Refresh"
capability "Polling"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
// Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
@@ -42,8 +47,8 @@ metadata {
}
standardTile("motion", "device.motion") {
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
@@ -56,16 +61,12 @@ metadata {
}
def refresh() {
log.debug "refresh..."
log.debug "refresh called"
poll()
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.pollChildren(this)
}
parent.pollChild()
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

@@ -19,34 +19,48 @@ metadata {
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Thermostat"
capability "Polling"
capability "Temperature Measurement"
capability "Sensor"
capability "Refresh"
capability "Refresh"
capability "Relative Humidity Measurement"
command "generateEvent"
command "raiseSetpoint"
command "lowerSetpoint"
command "resumeProgram"
command "switchMode"
command "generateEvent"
command "raiseSetpoint"
command "lowerSetpoint"
command "resumeProgram"
command "switchMode"
command "switchFanMode"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number"
}
simulator { }
tiles {
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
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"]
]
backgroundColors:[
// Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, 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"]
]
)
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
@@ -58,10 +72,9 @@ metadata {
state "updating", label:"Working", icon: "st.secondary.secondary"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
state "updating", label:"Working", icon: "st.secondary.secondary"
}
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
@@ -91,11 +104,14 @@ metadata {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary"
}
valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}%'
}
main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
}
preferences {
@@ -107,8 +123,6 @@ metadata {
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
// TODO: handle '' attribute
}
def refresh() {
@@ -119,9 +133,7 @@ def refresh() {
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
def results = parent.pollChild(this)
generateEvent(results) //parse received message from parent
parent.pollChild()
}
def generateEvent(Map results) {
@@ -133,16 +145,27 @@ def generateEvent(Map results) {
def isChange = false
def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name]
handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
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
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="thermostatFanMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
@@ -158,13 +181,19 @@ def generateEvent(Map results) {
//return descriptionText to be shown on mobile activity feed
private getThermostatDescriptionText(name, value, linkText) {
if(name == "temperature") {
return "$linkText temperature is $value°F"
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "$linkText temperature is $sendValue ${location.temperatureScale}"
} else if(name == "heatingSetpoint") {
return "heating setpoint is $value°F"
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "heating setpoint is $sendValue ${location.temperatureScale}"
} else if(name == "coolingSetpoint"){
return "cooling setpoint is $value°F"
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "cooling setpoint is $sendValue ${location.temperatureScale}"
} else if (name == "thermostatMode") {
return "thermostat mode is ${value}"
@@ -172,26 +201,26 @@ private getThermostatDescriptionText(name, value, linkText) {
} else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}"
} else if (name == "humidity") {
return "humidity is ${value} %"
} else {
return "${name} = ${value}"
}
}
void setHeatingSetpoint(setpoint) {
setHeatingSetpoint(setpoint.toDouble())
}
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
log.debug "***heating setpoint $setpoint"
def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
//enforce limits of heatingSetpoint
if (heatingSetpoint > 79) {
heatingSetpoint = 79
} else if (heatingSetpoint < 45) {
heatingSetpoint = 45
if (heatingSetpoint > maxHeatingSetpoint) {
heatingSetpoint = maxHeatingSetpoint
} else if (heatingSetpoint < minHeatingSetpoint) {
heatingSetpoint = minHeatingSetpoint
}
//enforce limits of heatingSetpoint vs coolingSetpoint
@@ -201,32 +230,34 @@ void setHeatingSetpoint(Double setpoint) {
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
log.error "Error setHeatingSetpoint(setpoint)"
}
}
void setCoolingSetpoint(setpoint) {
setCoolingSetpoint(setpoint.toDouble())
}
void setCoolingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
log.debug "***cooling setpoint $setpoint"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = setpoint
def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (coolingSetpoint > 92) {
coolingSetpoint = 92
} else if (coolingSetpoint < 65) {
coolingSetpoint = 65
if (coolingSetpoint > maxCoolingSetpoint) {
coolingSetpoint = maxCoolingSetpoint
} else if (coolingSetpoint < minCoolingSetpoint) {
coolingSetpoint = minCoolingSetpoint
}
//enforce limits of heatingSetpoint vs coolingSetpoint
@@ -236,15 +267,18 @@ void setCoolingSetpoint(Double setpoint) {
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
log.error "Error setCoolingSetpoint(setpoint)"
}
}
@@ -278,7 +312,7 @@ def modes() {
}
def fanModes() {
["off", "on", "auto", "circulate"]
["on", "auto"]
}
def switchMode() {
@@ -307,17 +341,15 @@ def switchFanMode() {
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = switchToFanMode("fanOn")
case "on":
returnCommand = switchToFanMode("auto")
break
case "fanOn":
returnCommand = switchToFanMode("fanCirculate")
break
case "fanCirculate":
returnCommand = switchToFanMode("fanAuto")
case "auto":
returnCommand = switchToFanMode("on")
break
}
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
returnCommand
}
@@ -326,25 +358,20 @@ def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode"
def returnCommand
if(nextMode == "fanAuto") {
if(!fanModes.contains("fanAuto")) {
if(nextMode == "auto") {
if(!fanModes.contains("auto")) {
returnCommand = fanAuto()
} else {
returnCommand = switchToFanMode("fanOn")
returnCommand = switchToFanMode("on")
}
} else if(nextMode == "fanOn") {
if(!fanModes.contains("fanOn")) {
} else if(nextMode == "on") {
if(!fanModes.contains("on")) {
returnCommand = fanOn()
} else {
returnCommand = switchToFanMode("fanCirculate")
}
} else if(nextMode == "fanCirculate") {
if(!fanModes.contains("fanCirculate")) {
returnCommand = fanCirculate()
} else {
returnCommand = switchToFanMode("fanAuto")
returnCommand = switchToFanMode("auto")
}
}
returnCommand
}
@@ -352,15 +379,16 @@ def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
def setThermostatMode(String mode) {
log.debug "setThermostatMode($mode)"
mode = mode.toLowerCase()
switchToMode(mode)
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
def setThermostatFanMode(String mode) {
log.debug "setThermostatFanMode($mode)"
mode = mode.toLowerCase()
switchToFanMode(mode)
}
def generateModeEvent(mode) {
@@ -368,7 +396,7 @@ def generateModeEvent(mode) {
}
def generateFanModeEvent(fanMode) {
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
}
def generateOperatingStateEvent(operatingState) {
@@ -403,6 +431,10 @@ def heat() {
generateStatusEvent()
}
def emergencyHeat() {
auxHeatOnly()
}
def auxHeatOnly() {
log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last()
@@ -447,26 +479,44 @@ def auto() {
def fanOn() {
log.debug "fanOn"
// parent.setFanMode (this,"on")
String fanMode = "on"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value
generateFanModeEvent(currentFanMode) // reset the tile back
}
}
def fanAuto() {
log.debug "fanAuto"
// parent.setFanMode (this,"auto")
String fanMode = "auto"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
}
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def fanCirculate() {
log.debug "fanCirculate"
// parent.setFanMode (this,"circulate")
}
def fanOff() {
log.debug "fanOff"
// parent.setFanMode (this,"off")
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value
generateFanModeEvent(currentFanMode) // reset the tile back
}
}
def generateSetpointEvent() {
@@ -476,20 +526,42 @@ def generateSetpointEvent() {
def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def heatingSetpoint = device.currentValue("heatingSetpoint")
log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
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)
}
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} else if (mode == "auto") {
@@ -499,9 +571,9 @@ def generateSetpointEvent() {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "emergencyHeat") {
} else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
}
@@ -510,26 +582,30 @@ def generateSetpointEvent() {
void raiseSetpoint() {
def mode = device.currentValue("thermostatMode")
def targetvalue
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").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else {
targetvalue = 0
}
targetvalue = targetvalue + 1
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
if (mode == "heat" && targetvalue > 79) {
targetvalue = 79
} else if (mode == "cool" && targetvalue > 92) {
targetvalue = 92
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
targetvalue = maxHeatingSetpoint
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
targetvalue = maxCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -543,25 +619,29 @@ void raiseSetpoint() {
void lowerSetpoint() {
def mode = device.currentValue("thermostatMode")
def targetvalue
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").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
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 as Integer
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else {
targetvalue = 0
}
targetvalue = targetvalue - 1
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
if (mode == "heat" && targetvalue.toInteger() < 45) {
targetvalue = 45
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
targetvalue = 65
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
targetvalue = minHeatingSetpoint
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
targetvalue = minCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -575,15 +655,15 @@ void lowerSetpoint() {
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat"){
if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
@@ -602,19 +682,22 @@ void alterSetpoint(temp) {
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
//step2: call parent.setHold to send http request to 3rd party cloud
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat"){
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
@@ -626,9 +709,9 @@ void alterSetpoint(temp) {
def generateStatusEvent() {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def temperature = device.currentValue("temperature").toInteger()
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature")
def statusText
@@ -643,14 +726,14 @@ def generateStatusEvent() {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint}° F"
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint}° F"
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
} else if (mode == "auto") {
@@ -660,7 +743,7 @@ def generateStatusEvent() {
statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") {
} else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat"
@@ -673,7 +756,18 @@ def generateStatusEvent() {
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -36,155 +36,71 @@
* Slider range from 0..100
* Change 9: 2015-03-06 (Juan Risso)
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
* Change 10: 2016-03-06 (Vinay Rao/Tom Manley)
* changed 2/3rds of the file to clean up code and add zigbee library improvements
*
*/
metadata {
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Sensor"
capability "Switch"
capability "Switch Level"
capability "Switch Level"
capability "Polling"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
}
}
// 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", nextState:"turningOff"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action: "switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, 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", "level", "levelSliderControl", "refresh"])
}
preferences {
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
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.light.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "refresh"])
}
preferences {
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.trace description
if (description?.startsWith("on/off:")) {
log.debug "The bulb was sent a command to do something just now..."
if (description[-1] == "1") {
def result = createEvent(name: "switch", value: "on")
log.debug "On command was sent maybe from manually turning on? : Parse returned ${result?.descriptionText}"
return result
} else if (description[-1] == "0") {
def result = createEvent(name: "switch", value: "off")
log.debug "Off command was sent : Parse returned ${result?.descriptionText}"
return result
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
sendEvent(resultMap)
}
}
def msg = zigbee.parse(description)
if (description?.startsWith("catchall:")) {
// log.trace msg
// log.trace "data: $msg.data"
def x = description[-4..-1]
// log.debug x
switch (x)
{
case "0000":
def result = createEvent(name: "switch", value: "off")
log.debug "${result?.descriptionText}"
return result
break
case "1000":
def result = createEvent(name: "switch", value: "off")
log.debug "${result?.descriptionText}"
return result
break
case "0100":
def result = createEvent(name: "switch", value: "on")
log.debug "${result?.descriptionText}"
return result
break
case "1001":
def result = createEvent(name: "switch", value: "on")
log.debug "${result?.descriptionText}"
return result
break
}
else {
log.debug "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
if (description?.startsWith("read attr")) {
// log.trace description[27..28]
// log.trace description[-2..-1]
if (description[27..28] == "0A") {
// log.debug description[-2..-1]
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
sendEvent( name: "level", value: i )
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
}
else {
if (description[-2..-1] == "00" && state.trigger == "setLevel") {
// log.debug description[-2..-1]
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
sendEvent( name: "level", value: i )
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
}
if (description[-2..-1] == state.lvl) {
// log.debug description[-2..-1]
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
sendEvent( name: "level", value: i )
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
}
}
}
}
def poll() {
[
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
]
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def updated() {
@@ -270,109 +186,63 @@ def updated() {
}
def on() {
state.lvl = "00"
state.trigger = "on/off"
// log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
zigbee.on()
}
def off() {
state.lvl = "00"
state.trigger = "on/off"
// log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
zigbee.off()
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
]
poll()
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
}
def setLevel(value) {
def cmds = []
value = value as Integer
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 0 {0000 ${state.rate}}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: value)
value = (value * 255 / 100)
def level = hex(value);
state.trigger = "setLevel"
state.lvl = "${level}"
def cmd
def delayForRefresh = 500
if (dimRate && (state?.rate != null)) {
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
def computedRate = convertRateValue(state.rate)
cmd = zigbee.setLevel(value, computedRate)
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
}
else {
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
cmd = zigbee.setLevel(value, 20)
delayForRefresh += 2000
}
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
}
log.debug cmds
cmds
int convertRateValue(rate) {
int convertedRate = 0
switch (rate)
{
case "0000":
convertedRate = 0
break
case "1500":
convertedRate = 20 //0015 hex in int is 2.1
break
case "2500":
convertedRate = 35 //0025 hex in int is 3.7
break
case "3500":
convertedRate = 50 //0035 hex in int is 5.1
break
}
convertedRate
}
def configure() {
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} 1 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500",
]
return configCmds + refresh() // send refresh cmds as part of config
log.debug "Configuring Reporting and Bindings."
return zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,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

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

View File

@@ -0,0 +1,227 @@
/**
* Hue Bloom
*
* Philips Hue Type "Color Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
results
}
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
} else {
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def 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
}
def verifyPercent(percent) {
if (percent == null)
return false
else if (percent >= 0 && percent <= 100) {
return true
} else {
log.warn "$percent is not 0-100"
return false
}
}

View File

@@ -17,16 +17,13 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
tileAttribute ("", key: "PRIMARY_CONTROL") {
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
}
}
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}'
}
}
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
}
}
}
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
state "default", label:'SN: ${currentValue}'
}
@@ -34,7 +31,7 @@ metadata {
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
}
main (["icon"])
main (["rich-control"])
details(["rich-control", "networkAddress"])
}
}
@@ -75,6 +72,7 @@ def parse(description) {
}
else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
}
}
}

View File

@@ -1,9 +1,11 @@
/**
* Hue Bulb
*
* Philips Hue Type "Extended Color Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
@@ -11,13 +13,14 @@ metadata {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
command "reset"
command "refresh"
}
simulator {
@@ -25,7 +28,7 @@ metadata {
}
tiles (scale: 2){
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
@@ -33,34 +36,48 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
}
main(["switch"])
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
@@ -68,17 +85,17 @@ def parse(description) {
}
// handle commands
def on() {
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
def off() {
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
def nextLevel() {
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
@@ -89,55 +106,105 @@ def nextLevel() {
setLevel(level)
}
def setLevel(percent) {
log.debug "Executing 'setLevel'"
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
def setSaturation(percent) {
log.debug "Executing 'setSaturation'"
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent)
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
def setHue(percent) {
log.debug "Executing 'setHue'"
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent)
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
def setColor(value) {
log.debug "setColor: ${value}, $this"
parent.setColor(this, value)
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
if (value.hex) { sendEvent(name: "color", value: value.hex)}
if (value.level) { sendEvent(name: "level", value: value.level)}
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
def reset() {
log.debug "Executing 'reset'"
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
void reset() {
log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
parent.poll()
}
def setAdjustedColor(value) {
if (value) {
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
adjusted.level = null
setColor(adjusted)
} else {
log.warn "Invalid color input"
}
}
def refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
@@ -156,3 +223,14 @@ def adjustOutgoingHue(percent) {
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
if (percent == null)
return false
else if (percent >= 0 && percent <= 100) {
return true
} else {
log.warn "$percent is not 0-100"
return false
}
}

View File

@@ -1,6 +1,8 @@
/**
* Hue Lux Bulb
*
* Philips Hue Type "Dimmable Light"
*
* Author: SmartThings
*/
// for the UI
@@ -12,21 +14,21 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
command "refresh"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
@@ -35,25 +37,18 @@ metadata {
attributeState "level", label: 'Level ${currentValue}%'
}
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["rich-control"])
details(["rich-control", "refresh"])
}
}
}
// parse events into attributes
@@ -74,23 +69,28 @@ def parse(description) {
}
// handle commands
def on() {
parent.on(this)
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
def off() {
parent.off(this)
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
def setLevel(percent) {
void setLevel(percent) {
log.debug "Executing 'setLevel'"
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else {
log.warn "$percent is not 0-100"
}
}
def refresh() {
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -84,6 +84,7 @@ def setHue(percentage) {
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setSaturation(percentage) {
@@ -97,6 +98,7 @@ def setSaturation(percentage) {
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColor(Map color) {
@@ -122,13 +124,15 @@ def setColor(Map color) {
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
if (resp.status < 300) {
sendEvent(name: "color", value: color.hex)
if (color.hex)
sendEvent(name: "color", value: color.hex)
sendEvent(name: "switch", value: "on")
events.each { sendEvent(it) }
} else {
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setLevel(percentage) {
@@ -150,6 +154,7 @@ def setLevel(percentage) {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColorTemperature(kelvin) {
@@ -165,6 +170,7 @@ def setColorTemperature(kelvin) {
}
}
return []
}
def on() {
@@ -174,6 +180,7 @@ def on() {
sendEvent(name: "switch", value: "on")
}
}
return []
}
def off() {
@@ -183,6 +190,7 @@ def off() {
sendEvent(name: "switch", value: "off")
}
}
return []
}
def poll() {

View File

@@ -84,6 +84,7 @@ def setLevel(percentage) {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColorTemperature(kelvin) {
@@ -99,6 +100,7 @@ def setColorTemperature(kelvin) {
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
}
}
return []
}
def on() {
@@ -108,6 +110,7 @@ def on() {
sendEvent(name: "switch", value: "on")
}
}
return []
}
def off() {
@@ -117,6 +120,7 @@ def off() {
sendEvent(name: "switch", value: "off")
}
}
return []
}
def poll() {

View File

@@ -0,0 +1,32 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: Mobile Presence i18n Translation File
#
# Filename: mobile-presence.src/i18n/messages.properties
#
# Change History:
# 1. 20160205 TW Initial release with informal Korean translation.
# 2. 20160224 TW Updated with formal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 설정
'''Set Device Image'''.ko=기기 이미지 설정
# Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
'''present'''.ko=집안
'''not present'''.ko=외출

View File

@@ -1,16 +1,28 @@
/**
* Copyright 2015 SmartThings
/*
===============================================================================
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* 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.
* 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.
===============================================================================
* Purpose: Mobile Presence DTH File
*
* Filename: mobile-presence.src/mobile-presence.groovy
*
* Change History:
* 1. 20160205 TW - Update/Edit to support i18n translations
===============================================================================
*/
metadata {
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
capability "Presence Sensor"
@@ -41,6 +53,7 @@ def parse(String description) {
def isStateChange = isStateChange(device, name, value)
def results = [
translatable: true,
name: name,
value: value,
unit: null,
@@ -72,8 +85,8 @@ private String parseValue(String description) {
private parseDescriptionText(String linkText, String value, String description) {
switch(value) {
case "present": return "$linkText has arrived"
case "not present": return "$linkText has left"
case "present": return "{{ linkText }} has arrived"
case "not present": return "{{ linkText }} has left"
default: return value
}
}
@@ -84,4 +97,4 @@ private getState(String value) {
case "not present": return "left"
default: return value
}
}
}

View File

@@ -1,235 +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.
*
* Samsung TV
*
* Author: SmartThings (juano23@gmail.com)
* Date: 2015-01-08
*/
metadata {
definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") {
capability "switch"
command "mute"
command "source"
command "menu"
command "tools"
command "HDMI"
command "Sleep"
command "Up"
command "Down"
command "Left"
command "Right"
command "chup"
command "chdown"
command "prech"
command "volup"
command "voldown"
command "Enter"
command "Return"
command "Exit"
command "Info"
command "Size"
}
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff"
}
standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) {
state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff"
}
standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff"
}
standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15"
}
standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Tools', action:"tools", icon:"st.secondary.tools"
}
standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Menu', action:"menu", icon:"st.vents.vent"
}
standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15"
}
standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10"
}
standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up"
}
standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down"
}
standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left"
}
standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right"
}
standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up"
}
standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down"
}
standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon"
}
standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up"
}
standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down"
}
standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark"
}
standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon"
}
standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked"
}
standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active"
}
standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open"
}
main "switch"
details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"])
}
def parse(String description) {
return null
}
def off() {
log.debug "Turning TV OFF"
parent.tvAction("POWEROFF",device.deviceNetworkId)
sendEvent(name:"Command", value: "Power Off", displayed: true)
}
def mute() {
log.trace "MUTE pressed"
parent.tvAction("MUTE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Mute", displayed: true)
}
def source() {
log.debug "SOURCE pressed"
parent.tvAction("SOURCE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Source", displayed: true)
}
def menu() {
log.debug "MENU pressed"
parent.tvAction("MENU",device.deviceNetworkId)
}
def tools() {
log.debug "TOOLS pressed"
parent.tvAction("TOOLS",device.deviceNetworkId)
sendEvent(name:"Command", value: "Tools", displayed: true)
}
def HDMI() {
log.debug "HDMI pressed"
parent.tvAction("HDMI",device.deviceNetworkId)
sendEvent(name:"Command sent", value: "Source", displayed: true)
}
def Sleep() {
log.debug "SLEEP pressed"
parent.tvAction("SLEEP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Sleep", displayed: true)
}
def Up() {
log.debug "UP pressed"
parent.tvAction("UP",device.deviceNetworkId)
}
def Down() {
log.debug "DOWN pressed"
parent.tvAction("DOWN",device.deviceNetworkId)
}
def Left() {
log.debug "LEFT pressed"
parent.tvAction("LEFT",device.deviceNetworkId)
}
def Right() {
log.debug "RIGHT pressed"
parent.tvAction("RIGHT",device.deviceNetworkId)
}
def chup() {
log.debug "CHUP pressed"
parent.tvAction("CHUP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Channel Up", displayed: true)
}
def chdown() {
log.debug "CHDOWN pressed"
parent.tvAction("CHDOWN",device.deviceNetworkId)
sendEvent(name:"Command", value: "Channel Down", displayed: true)
}
def prech() {
log.debug "PRECH pressed"
parent.tvAction("PRECH",device.deviceNetworkId)
sendEvent(name:"Command", value: "Prev Channel", displayed: true)
}
def Exit() {
log.debug "EXIT pressed"
parent.tvAction("EXIT",device.deviceNetworkId)
}
def volup() {
log.debug "VOLUP pressed"
parent.tvAction("VOLUP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Volume Up", displayed: true)
}
def voldown() {
log.debug "VOLDOWN pressed"
parent.tvAction("VOLDOWN",device.deviceNetworkId)
sendEvent(name:"Command", value: "Volume Down", displayed: true)
}
def Enter() {
log.debug "ENTER pressed"
parent.tvAction("ENTER",device.deviceNetworkId)
}
def Return() {
log.debug "RETURN pressed"
parent.tvAction("RETURN",device.deviceNetworkId)
}
def Info() {
log.debug "INFO pressed"
parent.tvAction("INFO",device.deviceNetworkId)
sendEvent(name:"Command", value: "Info", displayed: true)
}
def Size() {
log.debug "PICTURE_SIZE pressed"
parent.tvAction("PICTURE_SIZE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Picture Size", displayed: true)
}

View File

@@ -0,0 +1,35 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartPower Outlet i18n Translation File
#
# Filename: SmartPower-Outlet.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 설정
'''Outlet'''.ko= 스마트 플러그
# Events descriptionText
'''{{ device.displayName }} is On'''.ko={{ device.displayName }} 켜짐
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }} 꺼짐
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전력은 {{ value }}와트입니다.
'''On'''.ko= 켜짐
'''Off'''.ko=꺼짐
'''Turning On'''.ko=켜는 중
'''Turning Off'''.ko=끄는 중
#==============================================================================

View File

@@ -1,19 +1,26 @@
/**
* Copyright 2015 SmartThings
/*
===============================================================================
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* 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.
* 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.
===============================================================================
* Purpose: SmartPower Outlet DTH File
*
* SmartPower Outlet (CentraLite)
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy
*
* Author: SmartThings
* Date: 2015-08-23
* Change History:
* 1. 20160117 TW - Update/Edit to support i18n translations
===============================================================================
*/
metadata {
// Automatically generated. Make future change here.
@@ -58,10 +65,10 @@ metadata {
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"
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
}
tileAttribute ("power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'${currentValue} W'
@@ -91,22 +98,22 @@ def parse(String description) {
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
if (finalResult) {
log.info finalResult
log.info "final result = $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)
sendEvent(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
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)
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
}
}
else {

View File

@@ -0,0 +1,44 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartSense Moisture Sensor i18n Translation File
#
# Filename: SmartSense-Moisture-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with formal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Dry'''.ko=건조
'''Wet'''.ko=누수
'''dry'''.ko=건조
'''wet'''.ko=누수
'''battery'''.ko=배터리
'''Temperature Offset'''.ko=온도 직접 설정
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Degrees'''.ko=온도
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Give your device a name'''.ko=기기 이름 설정
'''Water Leak Sensor'''.ko=누수감지 센서
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
# Events descriptionText
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
#==============================================================================

View File

@@ -1,18 +1,29 @@
/**
* SmartSense Moisture Sensor
/*
===============================================================================
* Copyright 2016 SmartThings
*
* 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:
* 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.
* 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.
===============================================================================
* Purpose: SmartSense Moisture Sensor DTH File
*
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/
metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration"
@@ -43,7 +54,7 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input 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
}
}
@@ -116,16 +127,16 @@ private Map parseCatchAllMessage(String description) {
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
return resultMap
}
private boolean shouldProcessMessage(cluster) {
@@ -220,7 +231,8 @@ private Map getBatteryResult(rawValue) {
def result = [
name: 'battery',
value: '--'
value: '--',
translatable: true
]
def volts = rawValue / 10
@@ -228,7 +240,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -245,7 +257,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
@@ -253,7 +265,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
@@ -263,27 +275,37 @@ private Map getBatteryResult(rawValue) {
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}"
def descriptionText
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
translatable: true
]
}
private Map getMoistureResult(value) {
log.debug 'water'
String descriptionText = "${device.displayName} is ${value}"
log.debug "water"
def descriptionText
if ( value == "wet" )
descriptionText = '{{ device.displayName }} is wet'
else
descriptionText = '{{ device.displayName }} is dry'
return [
name: 'water',
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
translatable: true
]
}

View File

@@ -16,6 +16,7 @@ metadata {
capability "Water Sensor"
capability "Sensor"
capability "Battery"
capability "Temperature Measurement"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
@@ -39,17 +40,29 @@ metadata {
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
}
}
standardTile("temperature", "device.temperature", width: 2, height: 2) {
standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["water", "temperature"])
details(["water", "temperature", "battery"])
main (["water", "temperatureState"])
details(["water", "temperatureState", "temperature", "battery"])
}
}
@@ -115,7 +128,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map.descriptionText = "${device.displayName} is ${map.value}"
}
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
map.name = "temperature"
map.name = "temperatureState"
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
@@ -129,17 +142,30 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
{
def map = [:]
map.name = "water"
map.value = cmd.value ? "wet" : "dry"
map.descriptionText = "${device.displayName} is ${map.value}"
if(cmd.sensorType == 1) {
map.name = "temperature"
if(cmd.scale == 0) {
map.value = getTemperature(cmd.scaledSensorValue)
} else {
map.value = cmd.scaledSensorValue
}
map.unit = location.temperatureScale
}
map
}
def getTemperature(value) {
if(location.temperatureScale == "C"){
return value
} else {
return Math.round(celsiusToFahrenheit(value))
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd)
{
log.debug "COMMAND CLASS: $cmd"
}
}

View File

@@ -0,0 +1,43 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartSense Motion Sensor i18n Translation File
#
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with formal Korean translation.
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
#==============================================================================
# Korean (ko)
# Device Preferences
'''battery'''.ko=배터리
'''Temperature Offset'''.ko=온도 직접 설정
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Degrees'''.ko=온도
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Give your device a name'''.ko=기기 이름 설정
'''Motion Sensor'''.ko=모션 센서
'''motion'''.ko= 동작 감지
'''no motion'''.ko=동작 없음
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
# Events descriptionText
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}에서 움직임이 감지되었습니다.
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}에서 움직임이 중단되었습니다.
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
#==============================================================================

View File

@@ -1,17 +1,27 @@
/**
* SmartSense Motion/Temp Sensor
/*
===============================================================================
* Copyright 2016 SmartThings
*
* 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:
* 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.
* 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.
===============================================================================
* Purpose: SmartSense Motion Sensor DTH File
*
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/
metadata {
@@ -29,6 +39,7 @@ metadata {
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
}
@@ -46,7 +57,7 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input 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
}
}
@@ -235,7 +246,8 @@ private Map getBatteryResult(rawValue) {
def result = [
name: 'battery',
value: '--'
value: '--',
translatable: true
]
def volts = rawValue / 10
@@ -243,7 +255,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -260,7 +272,8 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
def value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
@@ -268,7 +281,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
@@ -278,28 +291,33 @@ private Map getBatteryResult(rawValue) {
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}"
def descriptionText
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
translatable: true
]
}
private Map getMotionResult(value) {
log.debug 'motion'
String linkText = getLinkText(device)
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
return [
name: 'motion',
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
translatable: true
]
}

View File

@@ -14,6 +14,8 @@
*
*/
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
@@ -25,10 +27,6 @@ metadata {
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
}
simulator {
@@ -233,7 +231,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {}
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."

View File

@@ -0,0 +1,50 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartSense Multi Sensor i18n Translation File
#
# Filename: SmartSense-Multi-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160117 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Yes'''.ko=
'''No'''.ko=아니요
'''battery'''.ko=배터리
'''Temperature Offset'''.ko=온도 직접 설정
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Degrees'''.ko=온도
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
'''Tap to set'''.ko=눌러서 설정
'''Give your device a name'''.ko=기기 이름 설정
'''Multipurpose Sensor'''.ko=문 및 창 센서
# Events descriptionText
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}에서 열림이 감지되었습니다.
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}에서 닫힘이 감지되었습니다.
'''{{ device.displayName }} was active'''.ko={{ device.displayName }} 활성화
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }} 비활성화
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
'''Inactive'''.ko=비활성 상태
'''Active'''.ko=활성 상태
'''Open'''.ko= 열림이 감지될 때
'''Closed'''.ko=닫힘
'''${currentValue}% battery'''.ko=${currentValue}% 배터리

View File

@@ -1,17 +1,27 @@
/**
* SmartSense Multi
/*
===============================================================================
* Copyright 2016 SmartThings
*
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* 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.
* 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.
===============================================================================
* Purpose: SmartSense Multi Sensor DTH File
*
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
*
* Change History:
* 1. 20160117 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/
metadata {
@@ -62,30 +72,30 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
}
}
tiles(scale: 2) {
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
tileAttribute ("device.status", 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"
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
}
}
standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
state("closed", label:'Closed', 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")
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
@@ -100,9 +110,6 @@ metadata {
]
)
}
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
@@ -112,7 +119,7 @@ metadata {
main(["status", "acceleration", "temperature"])
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
details(["status", "acceleration", "temperature", "battery", "refresh"])
}
}
@@ -276,19 +283,19 @@ def updated() {
if (garageSensor == "Yes") {
def descriptionText = "Updating device to garage sensor"
if (device.latestValue("status") == "open") {
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "closed") {
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
}
}
else {
def descriptionText = "Updating device to open/close sensor"
if (device.latestValue("status") == "garage-open") {
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "garage-closed") {
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
}
}
}
@@ -304,11 +311,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'battery',
value: '--'
value: '--',
translatable: true
]
def volts = rawValue / 10
@@ -316,7 +323,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -333,7 +340,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
@@ -341,7 +348,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
@@ -351,40 +358,50 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) {
log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
'{{ device.displayName }} was {{ value }}°F'
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
descriptionText: descriptionText,
translatable: true
]
}
private Map getContactResult(value) {
log.debug "Contact"
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
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)
}
private getAccelerationResult(numValue) {
log.debug "Acceleration"
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def value
def descriptionText
if ( numValue.endsWith("1") ) {
value = "active"
descriptionText = '{{ device.displayName }} was active'
} else {
value = "inactive"
descriptionText = '{{ device.displayName }} was inactive'
}
def isStateChange = isStateChange(device, name, value)
[
return [
name: name,
value: value,
descriptionText: descriptionText,
isStateChange: isStateChange
isStateChange: isStateChange,
translatable: true
]
}
@@ -441,28 +458,28 @@ def configure() {
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
@@ -481,17 +498,12 @@ def enrollResponse() {
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private Map parseAxis(String description) {
def hexToSignedInt = { hexVal ->
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def z = hexToSignedInt(description[0..3])
def y = hexToSignedInt(description[10..13])
def x = hexToSignedInt(description[20..23])
@@ -518,6 +530,11 @@ private Map parseAxis(String description) {
getXyzResult(xyzResults, description)
}
private hexToSignedInt(hexVal) {
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def garageEvent(zValue) {
def absValue = zValue.abs()
def contactValue = null
@@ -531,10 +548,9 @@ def garageEvent(zValue) {
garageValue = 'garage-open'
}
if (contactValue != null){
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
}
}

View File

@@ -22,6 +22,8 @@ metadata {
capability "Battery"
fingerprint profileId: "FC01", deviceId: "0139"
attribute "status", "string"
}
simulator {
@@ -72,15 +74,12 @@ metadata {
]
)
}
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main(["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "3axis", "battery"])
details(["contact", "acceleration", "temperature", "battery"])
}
}
@@ -102,7 +101,7 @@ def parse(String description) {
}
private Map parseSingleMessage(description) {
private List parseSingleMessage(description) {
def name = parseName(description)
def value = parseValue(description)
@@ -111,8 +110,9 @@ private Map parseSingleMessage(description) {
def handlerName = value == 'open' ? 'opened' : value
def isStateChange = isStateChange(device, name, value)
def results = [
name: name,
def results = []
results << createEvent(
name: "contact",
value: value,
unit: null,
linkText: linkText,
@@ -120,8 +120,18 @@ private Map parseSingleMessage(description) {
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
log.debug "Parse results for $device: $results"
)
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results
}
@@ -193,7 +203,7 @@ private List parseContactMessage(String description) {
parts.each { part ->
part = part.trim()
if (part.startsWith('contactState:')) {
results << getContactResult(part, description)
results.addAll(getContactResult(part, description))
}
else if (part.startsWith('accelerationState:')) {
results << getAccelerationResult(part, description)
@@ -272,7 +282,7 @@ private List parseRssiLqiMessage(String description) {
results
}
private getContactResult(part, description) {
private List getContactResult(part, description) {
def name = "contact"
def value = part.endsWith("1") ? "open" : "closed"
def handlerName = value == 'open' ? 'opened' : value
@@ -280,19 +290,33 @@ private getContactResult(part, description) {
def descriptionText = "$linkText was $handlerName"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
def results = []
results << createEvent(
name: "contact",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed:false
)
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results
}
private getAccelerationResult(part, description) {
private Map getAccelerationResult(part, description) {
def name = "acceleration"
def value = part.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
@@ -311,7 +335,7 @@ private getAccelerationResult(part, description) {
]
}
private getTempResult(part, description) {
private Map getTempResult(part, description) {
def name = "temperature"
def temperatureScale = getTemperatureScale()
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
@@ -336,7 +360,7 @@ private getTempResult(part, description) {
]
}
private getXyzResult(results, description) {
private Map getXyzResult(results, description) {
def name = "threeAxis"
def value = "${results.x},${results.y},${results.z}"
def linkText = getLinkText(device)
@@ -355,7 +379,7 @@ private getXyzResult(results, description) {
]
}
private getBatteryResult(part, description) {
private Map getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
@@ -376,7 +400,7 @@ private getBatteryResult(part, description) {
]
}
private getRssiResult(part, description, lastHop=false) {
private Map getRssiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopRssi" : "rssi"
def valueString = part.split(":")[1].trim()
def value = (Integer.parseInt(valueString) - 128).toString()
@@ -407,7 +431,7 @@ private getRssiResult(part, description, lastHop=false) {
* Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI.
*/
private getLqiResult(part, description, lastHop=false) {
private Map getLqiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopLqi" : "lqi"
def valueString = part.split(":")[1].trim()
def percentageOf = 255
@@ -452,6 +476,7 @@ private Boolean isOrientationMessage(String description) {
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
}
//Note: Not using this method anymore
private String parseName(String description) {
if (isSupportedDescription(description)) {
return "contact"

View File

@@ -13,6 +13,7 @@
* 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
metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -23,8 +24,7 @@
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
}
simulator {
@@ -225,7 +225,8 @@ def getTemperature(value) {
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {

View File

@@ -16,17 +16,18 @@
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
}
simulator {
@@ -219,7 +220,8 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {

View File

@@ -196,7 +196,8 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {

View File

@@ -32,14 +32,15 @@ metadata {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("heating", backgroundColor:"#ea5462")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {

View File

@@ -0,0 +1,225 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "carouselDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
command "tempUp"
command "tempDown"
command "heatUp"
command "heatDown"
command "coolUp"
command "coolDown"
command "setTemperature", ["number"]
}
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
main("thermostatMulti")
details([
"thermostatMulti"
])
}
}
def installed() {
sendEvent(name: "temperature", value: 72, unit: "F")
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
sendEvent(name: "thermostatMode", value: "off")
sendEvent(name: "thermostatFanMode", value: "fanAuto")
sendEvent(name: "thermostatOperatingState", value: "idle")
sendEvent(name: "humidity", value: 53, unit: "%")
}
def parse(String description) {
}
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
def threshold = 1.0
def current = device.currentValue("thermostatOperatingState")
def mode = device.currentValue("thermostatMode")
def heating = false
def cooling = false
def idle = false
if (mode in ["heat","emergency heat","auto"]) {
if (heatingSetpoint - temp >= threshold) {
heating = true
sendEvent(name: "thermostatOperatingState", value: "heating")
}
else if (temp - heatingSetpoint >= threshold) {
idle = true
}
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
}
if (mode in ["cool","auto"]) {
if (temp - coolingSetpoint >= threshold) {
cooling = true
sendEvent(name: "thermostatOperatingState", value: "cooling")
}
else if (coolingSetpoint - temp >= threshold && !heating) {
idle = true
}
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
}
else {
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
}
if (idle && !heating && !cooling) {
sendEvent(name: "thermostatOperatingState", value: "idle")
}
}
def setHeatingSetpoint(Double degreesF) {
log.debug "setHeatingSetpoint($degreesF)"
sendEvent(name: "heatingSetpoint", value: degreesF)
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
}
def setCoolingSetpoint(Double degreesF) {
log.debug "setCoolingSetpoint($degreesF)"
sendEvent(name: "coolingSetpoint", value: degreesF)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
}
def setThermostatMode(String value) {
sendEvent(name: "thermostatMode", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def setThermostatFanMode(String value) {
sendEvent(name: "thermostatFanMode", value: value)
}
def off() {
sendEvent(name: "thermostatMode", value: "off")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def heat() {
sendEvent(name: "thermostatMode", value: "heat")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def auto() {
sendEvent(name: "thermostatMode", value: "auto")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def emergencyHeat() {
sendEvent(name: "thermostatMode", value: "emergency heat")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def cool() {
sendEvent(name: "thermostatMode", value: "cool")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def fanOn() {
sendEvent(name: "thermostatFanMode", value: "fanOn")
}
def fanAuto() {
sendEvent(name: "thermostatFanMode", value: "fanAuto")
}
def fanCirculate() {
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
}
def tempUp() {
def ts = device.currentState("temperature")
def value = ts ? ts.integerValue + 1 : 72
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def tempDown() {
def ts = device.currentState("temperature")
def value = ts ? ts.integerValue - 1 : 72
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def setTemperature(value) {
def ts = device.currentState("temperature")
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def heatUp() {
def ts = device.currentState("heatingSetpoint")
def value = ts ? ts.integerValue + 1 : 68
sendEvent(name:"heatingSetpoint", value: value)
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
}
def heatDown() {
def ts = device.currentState("heatingSetpoint")
def value = ts ? ts.integerValue - 1 : 68
sendEvent(name:"heatingSetpoint", value: value)
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
}
def coolUp() {
def ts = device.currentState("coolingSetpoint")
def value = ts ? ts.integerValue + 1 : 76
sendEvent(name:"coolingSetpoint", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
}
def coolDown() {
def ts = device.currentState("coolingSetpoint")
def value = ts ? ts.integerValue - 1 : 76
sendEvent(name:"coolingSetpoint", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "colorWheelDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Color Control"
}
tiles(scale: 2) {
valueTile("currentColor", "device.color") {
state "default", label: '${currentValue}'
}
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
state "color", action: "color control.setColor"
}
main("currentColor")
details([
"rgbSelector"
])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
}
def setSaturation(percent) {
log.debug "Executing 'setSaturation'"
sendEvent(name: "saturation", value: percent)
}
def setHue(percent) {
log.debug "Executing 'setHue'"
sendEvent(name: "hue", value: percent)
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "presenceDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Presence Sensor"
command "arrived"
command "departed"
}
tiles(scale: 2) {
// You only get a presence tile view when the size is 3x3 otherwise it's a value tile
standardTile("presence", "device.presence", width: 3, height: 3, canChangeBackground: true) {
state("present", labelIcon:"st.presence.tile.mobile-present", backgroundColor:"#53a7c0")
state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ebeef2")
}
standardTile("notPresentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
state("not present", label:'not present', backgroundColor:"#ffffff", action:"departed")
}
standardTile("presentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
state("present", label:'present', backgroundColor:"#53a7c0", action:"arrived")
}
main("presence")
details([
"presence", "presenceControl", "notPresentBtn", "presentBtn"
])
}
}
def installed() {
sendEvent(name: "presence", value: "present")
}
def parse(String description) {
}
def arrived() {
log.trace "Executing 'arrived'"
sendEvent(name: "presence", value: "present")
}
def departed() {
log.trace "Executing 'arrived'"
sendEvent(name: "presence", value: "not present")
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "sliderDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Switch Level"
command "setRangedLevel", ["number"]
}
tiles(scale: 2) {
controlTile("tinySlider", "device.level", "slider", height: 2, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
controlTile("mediumSlider", "device.level", "slider", height: 2, width: 4, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
controlTile("largeSlider", "device.level", "slider", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
controlTile("rangeSlider", "device.rangedLevel", "slider", height: 2, width: 4, range: "(20..80)") {
state "level", action:"setRangedLevel"
}
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
state "default", label:'${currentValue}'
}
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
state "level", action:"setRangedLevel"
}
main("rangeValue")
details([
"tinySlider", "mediumSlider",
"largeSlider",
"rangeSlider", "rangeValue",
"rangeSliderConstrained"
])
}
}
def installed() {
sendEvent(name: "level", value: 63)
sendEvent(name: "rangedLevel", value: 47)
}
def parse(String description) {
}
def setLevel(value) {
log.debug "setting level to $value"
sendEvent(name:"level", value:value)
}
def setRangedLevel(value) {
log.debug "setting ranged level to $value"
sendEvent(name:"rangedLevel", value:value)
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "standardDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Switch"
}
tiles(scale: 2) {
// standard tile with actions
standardTile("actionRings", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
}
// standard flat tile with actions
standardTile("actionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true, decoration: "flat") {
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
}
// standard flat tile without actions
standardTile("noActionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: '${currentValue}',icon: "st.switches.switch.off", backgroundColor: "#ffffff"
state "on", label: '${currentValue}', icon: "st.switches.switch.on", backgroundColor: "#79b821"
}
// standard flat tile with only a label
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "default", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff"
}
// standard flat tile with icon and label
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "default", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff"
}
// standard flat tile with only icon (Refreh text is IN the icon file)
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
// standard with defaultState = true
standardTile("flatDefaultState", "null", width: 2, height: 2, decoration: "flat") {
state "off", label: 'Fail!', icon: "st.switches.switch.off"
state "on", label: 'Pass!', icon: "st.switches.switch.on", defaultState: true
}
// standard with implicit defaultState based on order (0 index is selected)
standardTile("flatImplicitDefaultState1", "null", width: 2, height: 2, decoration: "flat") {
state "on", label: 'Pass!', icon: "st.switches.switch.on"
state "off", label: 'Fail!', icon: "st.switches.switch.off"
}
// standard with implicit defaultState based on state.name == default
standardTile("flatImplicitDefaultState2", "null", width: 2, height: 2, decoration: "flat") {
state "off", label: 'Fail!', icon: "st.switches.switch.off"
state "default", label: 'Pass!', icon: "st.switches.switch.on"
}
// utility tiles to fill the spaces
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
state "default", label:''
}
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
state "default", label:''
}
main("standard1")
details([
"actionRings", "actionFlat", "noActionFlat",
"flatLabel", "flatIconLabel", "flatIcon",
"flatDefaultState", "flatImplicitDefaultState1", "flatImplicitDefaultState2",
])
}
}
def installed() {
sendEvent(name: "switch", value: "off")
}
def parse(String description) {
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "valueDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Sensor"
}
tiles(scale: 2) {
valueTile("text", "device.text", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("longText", "device.longText", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("integer", "device.integer", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("pi", "device.pi", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
valueTile("bgColor", "device.integer", width: 2, height: 2) {
state "default", label:'${currentValue}', backgroundColor: "#e86d13"
}
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#ff0000"],
[value: 90, color: "#0000ff"]
]
}
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#333333"]
]
}
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#990000"],
[value: 10, color: "#000099"]
]
}
valueTile("noValue", "device.nada", width: 2, height: 2) {
state "default", label:'${currentValue}'
}
main("text")
details([
"text", "longText", "integer",
"integerFloat", "pi", "floatAsText",
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
"bgColorRangeConflict", "noValue"
])
}
}
def installed() {
sendEvent(name: "text", value: "Test")
sendEvent(name: "longText", value: "The Longer The Text, The Better The Test")
sendEvent(name: "integer", value: 47)
sendEvent(name: "integerFloat", value: 47.0)
sendEvent(name: "pi", value: 3.14159)
sendEvent(name: "floatAsText", value: "3.14159")
}
def parse(String description) {
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "genericDeviceTile", namespace: "smartthings/tile-ux", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Switch Level"
command "levelUp"
command "levelDown"
command "randomizeLevel"
}
tiles(scale: 2) {
multiAttributeTile(name:"basicTile", type:"generic", width:6, height:4) {
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"
}
}
multiAttributeTile(name:"sliderTile", type:"generic", width:6, height:4) {
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel"
}
tileAttribute("device.level", key: "SLIDER_CONTROL") {
attributeState "default", action:"switch level.setLevel"
}
}
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
attributeState "default", label:'${currentValue}', backgroundColors:[
[value: 0, color: "#ff0000"],
[value: 20, color: "#ffff00"],
[value: 40, color: "#00ff00"],
[value: 60, color: "#00ffff"],
[value: 80, color: "#0000ff"],
[value: 100, color: "#ff00ff"]
]
}
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute("device.level", key: "VALUE_CONTROL") {
attributeState "VALUE_UP", action: "levelUp"
attributeState "VALUE_DOWN", action: "levelDown"
}
}
main(["basicTile"])
details(["basicTile", "sliderTile", "valueTile"])
}
}
def installed() {
}
def parse() {
// This is a simulated device. No incoming data to parse.
}
def on() {
log.debug "turningOn"
sendEvent(name: "switch", value: "on")
}
def off() {
log.debug "turningOff"
sendEvent(name: "switch", value: "off")
}
def setLevel(percent) {
log.debug "setLevel: ${percent}, this"
sendEvent(name: "level", value: percent)
}
def randomizeLevel() {
def level = Math.round(Math.random() * 100)
setLevel(level)
}
def levelUp() {
def level = device.latestValue("level") as Integer ?: 0
if (level < 100) {
level = level + 1
}
setLevel(level)
}
def levelDown() {
def level = device.latestValue("level") as Integer ?: 0
if (level > 0) {
level = level - 1
}
setLevel(level)
}

View File

@@ -0,0 +1,211 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "lightingDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Power Meter"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'Power level: ${currentValue}W', icon: "st.Appliances.appliances17"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
multiAttributeTile(name:"switchNoPower", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
multiAttributeTile(name:"switchNoSlider", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'The power level is currently: ${currentValue}W', icon: "st.Appliances.appliances17"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
multiAttributeTile(name:"switchNoSliderOrColor", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'The light is currently consuming this amount of power: ${currentValue}W', icon: "st.Appliances.appliances17"
}
}
valueTile("color", "device.color", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "color", label: '${currentValue}'
}
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "switchNoPower", "switchNoSlider", "switchNoSliderOrColor", "color", "refresh", "reset"])
}
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
results
}
// handle commands
def on() {
//log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
def off() {
//log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
def nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
def setLevel(percent) {
log.debug "setLevel: ${percent}, this"
sendEvent(name: "level", value: percent)
def power = Math.round(percent / 1.175) * 0.1
sendEvent(name: "power", value: power)
}
def setSaturation(percent) {
log.debug "setSaturation: ${percent}, $this"
sendEvent(name: "saturation", value: percent)
}
def setHue(percent) {
log.debug "setHue: ${percent}, $this"
sendEvent(name: "hue", value: percent)
}
def setColor(value) {
log.debug "setColor: ${value}, $this"
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
if (value.hex) { sendEvent(name: "color", value: value.hex)}
if (value.level) { sendEvent(name: "level", value: value.level)}
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
}
def reset() {
log.debug "Executing 'reset'"
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
//parent.poll()
}
def setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
}
}
def refresh() {
log.debug "Executing 'refresh'"
//parent.manualRefresh()
}
def 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

@@ -0,0 +1,122 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "mediaPlayerDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Music Player"
}
tiles(scale: 2) {
multiAttributeTile(name: "mediaMulti", type:"mediaPlayer", width:6, height:4) {
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
attributeState("paused", label:"Paused",)
attributeState("playing", label:"Playing")
attributeState("stopped", label:"Stopped")
}
tileAttribute("device.status", key: "MEDIA_STATUS") {
attributeState("paused", label:"Paused", action:"music Player.play", nextState: "playing")
attributeState("playing", label:"Playing", action:"music Player.pause", nextState: "paused")
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
}
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
attributeState("default", action:"music Player.previousTrack")
}
tileAttribute("device.status", key: "NEXT_TRACK") {
attributeState("default", action:"music Player.nextTrack")
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState("level", action:"music Player.setLevel")
}
tileAttribute ("device.mute", key: "MEDIA_MUTED") {
attributeState("unmuted", action:"music Player.mute", nextState: "muted")
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
}
tileAttribute("device.trackDescription", key: "MARQUEE") {
attributeState("default", label:"${currentValue}")
}
}
main "mediaMulti"
details(["mediaMulti"])
}
}
def installed() {
state.tracks = [
"Gangnam Style (강남스타일)\nPSY\nPsy 6 (Six Rules), Part 1",
"Careless Whisper\nWham!\nMake It Big",
"Never Gonna Give You Up\nRick Astley\nWhenever You Need Somebody",
"Shake It Off\nTaylor Swift\n1989",
"Ironic\nAlanis Morissette\nJagged Little Pill",
"Hotline Bling\nDrake\nHotline Bling - Single"
]
state.currentTrack = 0
sendEvent(name: "level", value: 72)
sendEvent(name: "mute", value: "unmuted")
sendEvent(name: "status", value: "stopped")
}
def parse(description) {
// No parsing will happen with this simulated device.
}
def play() {
sendEvent(name: "status", value: "playing")
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def pause() {
sendEvent(name: "status", value: "paused")
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def stop() {
sendEvent(name: "status", value: "stopped")
}
def previousTrack() {
state.currentTrack = state.currentTrack - 1
if (state.currentTrack < 0)
state.currentTrack = state.tracks.size()-1
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def nextTrack() {
state.currentTrack = state.currentTrack + 1
if (state.currentTrack == state.tracks.size())
state.currentTrack = 0
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def mute() {
sendEvent(name: "mute", value: "muted")
}
def unmute() {
sendEvent(name: "mute", value: "unmuted")
}
def setLevel(level) {
sendEvent(name: "level", value: level)
}

View File

@@ -0,0 +1,341 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "thermostatDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
command "tempUp"
command "tempDown"
command "heatUp"
command "heatDown"
command "coolUp"
command "coolDown"
command "setTemperature", ["number"]
}
tiles(scale: 2) {
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF",
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"]
])
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}', unit:"dF",
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"]
]
)
}
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"tempDown"
}
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"tempUp"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
}
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"heatDown"
}
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"heatUp"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
}
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"coolDown"
}
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"coolUp"
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
}
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
}
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
state "idle", label:'${name}', backgroundColor:"#ffffff"
state "heating", label:'${name}', backgroundColor:"#ffa81e"
state "cooling", label:'${name}', backgroundColor:"#269bd2"
}
main("thermostatFull")
details([
"thermostatFull", "thermostatNoHumidity", "thermostatBasic",
"temperature","tempDown","tempUp",
"mode", "fanMode", "operatingState",
"heatingSetpoint", "heatDown", "heatUp",
"coolingSetpoint", "coolDown", "coolUp"
])
}
}
def installed() {
sendEvent(name: "temperature", value: 72, unit: "F")
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
sendEvent(name: "thermostatMode", value: "off")
sendEvent(name: "thermostatFanMode", value: "fanAuto")
sendEvent(name: "thermostatOperatingState", value: "idle")
sendEvent(name: "humidity", value: 53, unit: "%")
}
def parse(String description) {
}
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
def threshold = 1.0
def current = device.currentValue("thermostatOperatingState")
def mode = device.currentValue("thermostatMode")
def heating = false
def cooling = false
def idle = false
if (mode in ["heat","emergency heat","auto"]) {
if (heatingSetpoint - temp >= threshold) {
heating = true
sendEvent(name: "thermostatOperatingState", value: "heating")
}
else if (temp - heatingSetpoint >= threshold) {
idle = true
}
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
}
if (mode in ["cool","auto"]) {
if (temp - coolingSetpoint >= threshold) {
cooling = true
sendEvent(name: "thermostatOperatingState", value: "cooling")
}
else if (coolingSetpoint - temp >= threshold && !heating) {
idle = true
}
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
}
else {
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
}
if (mode == "off") {
idle = true
}
if (idle && !heating && !cooling) {
sendEvent(name: "thermostatOperatingState", value: "idle")
}
}
def setHeatingSetpoint(Double degreesF) {
log.debug "setHeatingSetpoint($degreesF)"
sendEvent(name: "heatingSetpoint", value: degreesF)
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
}
def setCoolingSetpoint(Double degreesF) {
log.debug "setCoolingSetpoint($degreesF)"
sendEvent(name: "coolingSetpoint", value: degreesF)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
}
def setThermostatMode(String value) {
sendEvent(name: "thermostatMode", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def setThermostatFanMode(String value) {
sendEvent(name: "thermostatFanMode", value: value)
}
def off() {
sendEvent(name: "thermostatMode", value: "off")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def heat() {
sendEvent(name: "thermostatMode", value: "heat")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def auto() {
sendEvent(name: "thermostatMode", value: "auto")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def emergencyHeat() {
sendEvent(name: "thermostatMode", value: "emergency heat")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def cool() {
sendEvent(name: "thermostatMode", value: "cool")
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def fanOn() {
sendEvent(name: "thermostatFanMode", value: "fanOn")
}
def fanAuto() {
sendEvent(name: "thermostatFanMode", value: "fanAuto")
}
def fanCirculate() {
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
}
def poll() {
null
}
def tempUp() {
def ts = device.currentState("temperature")
def value = ts ? ts.integerValue + 1 : 72
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def tempDown() {
def ts = device.currentState("temperature")
def value = ts ? ts.integerValue - 1 : 72
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def setTemperature(value) {
def ts = device.currentState("temperature")
sendEvent(name:"temperature", value: value)
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
}
def heatUp() {
def ts = device.currentState("heatingSetpoint")
def value = ts ? ts.integerValue + 1 : 68
sendEvent(name:"heatingSetpoint", value: value)
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
}
def heatDown() {
def ts = device.currentState("heatingSetpoint")
def value = ts ? ts.integerValue - 1 : 68
sendEvent(name:"heatingSetpoint", value: value)
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
}
def coolUp() {
def ts = device.currentState("coolingSetpoint")
def value = ts ? ts.integerValue + 1 : 76
sendEvent(name:"coolingSetpoint", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
}
def coolDown() {
def ts = device.currentState("coolingSetpoint")
def value = ts ? ts.integerValue - 1 : 76
sendEvent(name:"coolingSetpoint", value: value)
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
}

View File

@@ -0,0 +1,169 @@
/**
* Copyright 2016 SmartThings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (
name: "videoPlayerDeviceTile",
namespace: "smartthings/tile-ux",
author: "SmartThings") {
capability "Configuration"
capability "Video Camera"
capability "Video Capture"
capability "Refresh"
capability "Switch"
// custom commands
command "start"
command "stop"
command "setProfileHD"
command "setProfileSDH"
command "setProfileSDL"
}
tiles(scale: 2) {
multiAttributeTile(name: "videoPlayer", type: "videoPlayer", width: 6, height: 4) {
tileAttribute("device.switch", key: "CAMERA_STATUS") {
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
}
tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
attributeState("errorMessage", label: "", value: "", defaultState: true)
}
tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
}
tileAttribute("device.startLive", key: "START_LIVE") {
attributeState("live", action: "start", defaultState: true)
}
tileAttribute("device.stream", key: "STREAM_URL") {
attributeState("activeURL", defaultState: true)
}
tileAttribute("device.profile", key: "STREAM_QUALITY") {
attributeState("1", label: "720p", action: "setProfileHD", defaultState: true)
attributeState("2", label: "h360p", action: "setProfileSDH", defaultState: true)
attributeState("3", label: "l360p", action: "setProfileSDL", defaultState: true)
}
tileAttribute("device.betaLogo", key: "BETA_LOGO") {
attributeState("betaLogo", label: "", value: "", defaultState: true)
}
}
multiAttributeTile(name: "videoPlayerMin", type: "videoPlayer", width: 6, height: 4) {
tileAttribute("device.switch", key: "CAMERA_STATUS") {
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
}
tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
attributeState("errorMessage", label: "", value: "", defaultState: true)
}
tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
}
tileAttribute("device.startLive", key: "START_LIVE") {
attributeState("live", action: "start", defaultState: true)
}
tileAttribute("device.stream", key: "STREAM_URL") {
attributeState("activeURL", defaultState: true)
}
}
main("videoPlayer")
details([
"videoPlayer", "videoPlayerMin"
])
}
}
def installed() {
}
def parse(String description) {
}
def refresh() {
log.trace "refresh()"
// no-op
}
def on() {
log.trace "on()"
// no-op
}
def off() {
log.trace "off()"
// no-op
}
def setProfile(profile) {
log.trace "setProfile(): ${profile}"
sendEvent(name: "profile", value: profile, displayed: false)
}
def setProfileHD() {
setProfile(1)
}
def setProfileSDH() {
setProfile(2)
}
def setProfileSDL() {
setProfile(3)
}
def start() {
log.trace "start()"
def dataLiveVideo = [
OutHomeURL : "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
InHomeURL : "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
ThumbnailURL: "http://cdn.device-icons.smartthings.com/camera/dlink-indoor@2x.png",
cookie : [key: "key", value: "value"]
]
def event = [
name : "stream",
value : groovy.json.JsonOutput.toJson(dataLiveVideo).toString(),
data : groovy.json.JsonOutput.toJson(dataLiveVideo),
descriptionText: "Starting the livestream",
eventType : "VIDEO",
displayed : false,
isStateChange : true
]
sendEvent(event)
}
def stop() {
log.trace "stop()"
}

View File

@@ -33,7 +33,7 @@ metadata {
state "power", label: '${currentValue} W'
}
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: "www.wattvision.com" , url: '${currentValue}', width: 3, height: 2)
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: ["www.wattvision.com"] , url: '${currentValue}', width: 3, height: 2)
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"

View File

@@ -0,0 +1,194 @@
/**
* Iris Smart Fob
*
* Copyright 2015 Mitch Pond
* Presence code adapted from SmartThings Arrival Sensor HA device type
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Battery"
capability "Button"
capability "Configuration"
capability "Presence Sensor"
capability "Sensor"
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
}
preferences{
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
defaultValue: 3, displayDuringSetup: false)
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
input "logging", "bool", title: "Enable debug logging",
defaultValue: false, displayDuringSetup: false
}
tiles(scale: 2) {
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["presence"])
details(["presence","button","battery"])
}
}
def parse(String description) {
def descMap = zigbee.parseDescriptionAsMap(description)
logIt descMap
state.lastCheckin = now()
logIt "lastCheckin = ${state.lastCheckin}"
handlePresenceEvent(true)
def results = []
if (description?.startsWith('catchall:'))
results = parseCatchAllMessage(descMap)
else if (description?.startsWith('read attr -'))
results = parseReportAttributeMessage(descMap)
else logIt(descMap, "trace")
return results;
}
def updated() {
startTimer()
configure()
}
def configure(){
logIt "Configuring Smart Fob..."
[
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
] +
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
}
def parseCatchAllMessage(descMap) {
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
handleButtonPress(descMap.sourceEndpoint as int)
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
handleButtonRelease(descMap.sourceEndpoint as int)
else logIt("Parse: Unhandled message: ${descMap}","trace")
}
def parseReportAttributeMessage(descMap) {
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
else logIt descMap
}
private createBatteryEvent(percent) {
logIt "Battery level at " + percent
return createEvent([name: "battery", value: percent])
}
//this method determines if a press should count as a push or a hold and returns the relevant event type
private handleButtonRelease(button) {
logIt "lastPress state variable: ${state.lastPress}"
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
if (!state.lastPress) return sequenceError()
else if (state.lastPress.button != button) return sequenceError()
def currentTime = now()
def startOfPress = state.lastPress?.time
def timeDif = currentTime - startOfPress
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
if (timeDif < 0)
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
return sequenceError()
else if (timeDif < holdTimeMillisec)
return createButtonEvent(button,"pushed")
else
return createButtonEvent(button,"held")
}
private handleButtonPress(button) {
state.lastPress = [button: button, time: now()]
}
private createButtonEvent(button,action) {
logIt "Button ${button} ${action}"
return createEvent([
name: "button",
value: action,
data:[buttonNumber: button],
descriptionText: "${device.displayName} button ${button} was ${action}",
isStateChange: true,
displayed: true])
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
logIt "Sensor is present"
startTimer()
} else if (!present) {
logIt "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
logIt "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
logIt "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
logIt "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}
// ****** Utility functions ******
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }

View File

@@ -44,7 +44,7 @@ metadata {
attributeState "power", label:'${currentValue} W'
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
@@ -56,21 +56,17 @@ metadata {
def parse(String description) {
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else if (resultMap.type == "power") {
def powerValue
def event = zigbee.getEvent(description)
if (event) {
log.info event
if (event.name == "power") {
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(name: "power", value: powerValue)
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(event)
}
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
sendEvent(event)
}
}
else {

View File

@@ -39,7 +39,7 @@ metadata {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
@@ -51,15 +51,9 @@ metadata {
def parse(String description) {
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
def event = zigbee.getEvent(description)
if (event) {
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -63,7 +63,7 @@ metadata {
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.switch", inactiveLabel: false, decoration: "flat") {
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) {

View File

@@ -23,22 +23,14 @@
capability "Battery"
capability "Configuration"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
}
tiles(scale: 2) {
@@ -60,7 +52,7 @@
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
@@ -91,32 +83,19 @@ def uninstalled() {
}
def configure() {
/*
def cmds =
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
"${TYPE_ENUM8}", 0, 3600, "{01}") +
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
"${TYPE_U8}", 600, 21600, "{01}")
*/
def zigbeeId = device.zigbeeId
def cmds =
[
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
]
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
TYPE_ENUM8, 0, 3600, null) +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
TYPE_U8, 600, 21600, 0x01)
log.info "configure() --- cmds: $cmds"
return cmds + refresh() // send refresh cmds as part of config
}
def refresh() {
def cmds =
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
log.info "refresh() --- cmds: $cmds"
return cmds
}
@@ -129,34 +108,27 @@ def parse(String description) {
map = parseReportAttributeMessage(description)
}
log.debug "parse() --- Parse returned $map"
def result = map ? createEvent(map) : null
log.debug "parse() --- returned: $result"
return result
}
// Lock capability commands
def lock() {
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
//log.info "lock() -- cmds: $cmds"
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR)
log.info "lock() -- cmds: $cmds"
return cmds
}
def unlock() {
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
//log.info "unlock() -- cmds: $cmds"
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR)
log.info "unlock() -- cmds: $cmds"
return cmds
}
// Private methods
private Map parseReportAttributeMessage(String description) {
log.trace "parseReportAttributeMessage() --- description: $description"
Map descMap = zigbee.parseDescriptionAsMap(description)
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
Map resultMap = [:]
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
resultMap.name = "battery"
@@ -164,18 +136,24 @@ private Map parseReportAttributeMessage(String description) {
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
resultMap.value = Integer.parseInt(descMap.value, 16)
}
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
}
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
def value = Integer.parseInt(descMap.value, 16)
def linkText = getLinkText(device)
resultMap.name = "lock"
resultMap.putAll([0:["value":"unknown",
"descriptionText":"Not fully locked"],
1:["value":"locked"],
2:["value":"unlocked"]].get(value,
["value":"unknown",
"descriptionText":"Unknown lock state"]))
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
if (value == 0) {
resultMap.value = "unknown"
resultMap.descriptionText = "${linkText} is not fully locked"
} else if (value == 1) {
resultMap.value = "locked"
resultMap.descriptionText = "${linkText} is locked"
} else if (value == 2) {
resultMap.value = "unlocked"
resultMap.descriptionText = "${linkText} is unlocked"
} else {
resultMap.value = "unknown"
resultMap.descriptionText = "${linkText} is in unknown lock state"
}
}
else {
log.debug "parseReportAttributeMessage() --- ignoring attribute"

View File

@@ -57,7 +57,7 @@ metadata {
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}

View File

@@ -25,6 +25,7 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019", manufacturer: "SmartThings", model: "outletv4", deviceJoinName: "Outlet"
}
tiles(scale: 2) {
@@ -39,7 +40,7 @@ metadata {
attributeState "power", label:'${currentValue} W'
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
@@ -50,22 +51,15 @@ metadata {
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else if (resultMap.type == "power") {
def event = zigbee.getEvent(description)
if (event) {
if (event.name == "power") {
def powerValue
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(name: "power", value: powerValue)
}
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(name: "power", value: powerValue)
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
sendEvent(event)
}
}
else {

View File

@@ -42,7 +42,7 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
@@ -53,16 +53,9 @@ metadata {
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
def event = zigbee.getEvent(description)
if (event) {
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -54,7 +54,7 @@ metadata {
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
@@ -73,16 +73,9 @@ metadata {
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getKnownDescription(description)
if (finalResult) {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
def event = zigbee.getEvent(description)
if (event) {
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -245,6 +245,7 @@ def retypeBasedOnMSR() {
break
case "011F-0001-0001": // Schlage motion
case "014A-0001-0001": // Ecolink motion
case "014A-0004-0001": // Ecolink motion +
case "0060-0001-0002": // Everspring SP814
case "0060-0001-0003": // Everspring HSP02
case "011A-0601-0901": // Enerwave ZWN-BPC

View File

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

View File

@@ -57,7 +57,7 @@ def parse(String description) {
return result
}
def sensorValueEvent(Short value) {
def sensorValueEvent(value) {
if (value) {
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
} else {
@@ -94,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
{
def result = []
if (cmd.notificationType == 0x07) {
if (cmd.event == 0x01 || cmd.event == 0x02) {
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors
result << sensorValueEvent(cmd.v1AlarmLevel)
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x00) {
result << sensorValueEvent(0)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(zwave.batteryV1.batteryGet())
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) {
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << sensorValueEvent(1)
}
} else if (cmd.notificationType) {
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, isStateChange: true, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
}
result
}
@@ -119,6 +119,10 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
if (state.MSR == "011A-0601-0901" && device.currentState('motion') == null) { // Enerwave motion doesn't always get the associationSet that the hub sends on join
result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
}
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
result << response(zwave.batteryV1.batteryGet())
result << response("delay 1200")
@@ -179,4 +183,4 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
result
}
}

View File

@@ -61,37 +61,44 @@ def parse(String description) {
zwaveEvent(cmd, results)
}
}
// log.debug "\"$description\" parsed to ${results.inspect()}"
log.debug "'$description' parsed to ${results.inspect()}"
return results
}
def createSmokeOrCOEvents(name, results) {
def text = null
if (name == "smoke") {
text = "$device.displayName smoke was detected!"
// these are displayed:false because the composite event is the one we want to see in the app
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
} else if (name == "carbonMonoxide") {
text = "$device.displayName carbon monoxide was detected!"
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
} else if (name == "tested") {
text = "$device.displayName was tested"
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
} else if (name == "smokeClear") {
text = "$device.displayName smoke is clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
name = "clear"
} else if (name == "carbonMonoxideClear") {
text = "$device.displayName carbon monoxide is clear"
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
name = "clear"
} else if (name == "testClear") {
text = "$device.displayName smoke is clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
name = "clear"
switch (name) {
case "smoke":
text = "$device.displayName smoke was detected!"
// these are displayed:false because the composite event is the one we want to see in the app
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
break
case "carbonMonoxide":
text = "$device.displayName carbon monoxide was detected!"
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
break
case "tested":
text = "$device.displayName was tested"
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
break
case "smokeClear":
text = "$device.displayName smoke is clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
name = "clear"
break
case "carbonMonoxideClear":
text = "$device.displayName carbon monoxide is clear"
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
name = "clear"
break
case "testClear":
text = "$device.displayName test cleared"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
name = "clear"
break
}
// This composite event is used for updating the tile
results << createEvent(name: "alarmState", value: name, descriptionText: text)
@@ -117,8 +124,10 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
break
case 13: // sent every hour -- not sure what this means, just a wake up notification?
if (cmd.alarmLevel != 255) {
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
if (cmd.alarmLevel == 255) {
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
} else {
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
}
// Clear smoke in case they pulled batteries and we missed the clear msg
@@ -127,9 +136,8 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
}
// Check battery if we don't have a recent battery event
def prevBattery = device.currentState("battery")
if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
results << response(zwave.batteryV1.batteryGet())
}
break
default:
@@ -158,12 +166,17 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
results << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format())
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
} else {
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
def map = [ name: "battery", unit: "%" ]
def map = [ name: "battery", unit: "%", isStateChange: true ]
state.lastbatt = now()
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "$device.displayName battery is low!"

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Thu Feb 25 08:56:06 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip

160
gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

19
settings.gradle Normal file
View File

@@ -0,0 +1,19 @@
/*
* This settings file was auto generated by the Gradle buildInit task
* by 'jblaisdell' at '2/25/16 8:56 AM' with Gradle 2.10
*
* The settings file is used to specify which projects to include in your build.
* In a single project build this file can be empty or even removed.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user guide at https://docs.gradle.org/2.10/userguide/multi_project_builds.html
*/
/*
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/
rootProject.name = 'SmartThingsPublic'

View File

@@ -0,0 +1,50 @@
/**
* 가상세상2
*
* Copyright 2016 이똥구
*
* 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: "가상세상2",
namespace: "가상세상2",
author: "이똥구",
description: "\uAC00\uC0C1\uC138\uC0C12",
category: "Fun & Social",
iconUrl: "http://icdn.pro/images/fr/v/i/vie-champignons-icone-5438-128.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")
preferences {
section("Title") {
// TODO: put inputs here
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
}
// TODO: implement event handlers

View File

@@ -0,0 +1,50 @@
/**
* 가상5
*
* Copyright 2016 이똥구
*
* 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: "가상5",
namespace: "가상5",
author: "이똥구",
description: "\uAC00\uC0C15",
category: "Family",
iconUrl: "http://icones.pro/feu-fleurs-nature-image-png.html",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("Title") {
// TODO: put inputs here
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
}
// TODO: implement event handlers

View File

@@ -0,0 +1,147 @@
/**
* BeaconThing Manager
*
* Copyright 2015 obycode
*
* 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: "BeaconThings Manager",
namespace: "com.obycode",
author: "obycode",
description: "SmartApp to interact with the BeaconThings iOS app. Use this app to integrate iBeacons into your smart home.",
category: "Convenience",
iconUrl: "http://beaconthingsapp.com/images/Icon-60.png",
iconX2Url: "http://beaconthingsapp.com/images/Icon-60@2x.png",
iconX3Url: "http://beaconthingsapp.com/images/Icon-60@3x.png",
oauth: true)
preferences {
section("Allow BeaconThings to talk to your home") {
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def initialize() {
}
def uninstalled() {
removeChildDevices(getChildDevices())
}
mappings {
path("/beacons") {
action: [
DELETE: "clearBeacons",
POST: "addBeacon"
]
}
path("/beacons/:id") {
action: [
PUT: "updateBeacon",
DELETE: "deleteBeacon"
]
}
}
void clearBeacons() {
removeChildDevices(getChildDevices())
}
void addBeacon() {
def beacon = request.JSON?.beacon
if (beacon) {
def beaconId = "BeaconThings"
if (beacon.major) {
beaconId = "$beaconId-${beacon.major}"
if (beacon.minor) {
beaconId = "$beaconId-${beacon.minor}"
}
}
log.debug "adding beacon $beaconId"
def d = addChildDevice("com.obycode", "BeaconThing", beaconId, null, [label:beacon.name, name:"BeaconThing", completedSetup: true])
log.debug "addChildDevice returned $d"
if (beacon.present) {
d.arrive(beacon.present)
}
else if (beacon.presence) {
d.setPresence(beacon.presence)
}
}
}
void updateBeacon() {
log.debug "updating beacon ${params.id}"
def beaconDevice = getChildDevice(params.id)
// def children = getChildDevices()
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
if (!beaconDevice) {
log.debug "Beacon not found directly"
def children = getChildDevices()
beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
if (!beaconDevice) {
log.debug "Beacon not found in list either"
return
}
}
// This could be just updating the presence
def presence = request.JSON?.presence
if (presence) {
log.debug "Setting ${beaconDevice.label} to $presence"
beaconDevice.setPresence(presence)
}
// It could be someone arriving
def arrived = request.JSON?.arrived
if (arrived) {
log.debug "$arrived arrived at ${beaconDevice.label}"
beaconDevice.arrived(arrived)
}
// It could be someone left
def left = request.JSON?.left
if (left) {
log.debug "$left left ${beaconDevice.label}"
beaconDevice.left(left)
}
// or it could be updating the name
def beacon = request.JSON?.beacon
if (beacon) {
beaconDevice.label = beacon.name
}
}
void deleteBeacon() {
log.debug "deleting beacon ${params.id}"
deleteChildDevice(params.id)
// def children = getChildDevices()
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
// if (beaconDevice) {
// deleteChildDevice(beaconDevice.deviceNetworkId)
// }
}
private removeChildDevices(delete) {
delete.each {
deleteChildDevice(it.deviceNetworkId)
}
}

View File

@@ -67,11 +67,11 @@ mappings {
def listAllDevices() {
def resp = []
switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub?.name]
}
locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub?.name]
}
return resp
}

View File

@@ -1,117 +0,0 @@
definition(
name: "EyXAr Notifications",
namespace: "eyxar",
author: "EyXAr",
description: "Phone and Voice notification of your door sensor status and phone presence sensor autonitification.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/for-st/For_ST_60px.png",
iconX2Url: "https://s3.amazonaws.com/for-st/For_ST_120px.png",
iconX3Url: "https://s3.amazonaws.com/for-st/For_ST_256px.png"
)
/* For ST will only work if EyXAr Notification is installed and set-up first. */
preferences {
section("EyXAr Auto Notifications - For Voice Notification, Install the app 'FOR ST' in Google Play")
{
input "door", "capability.contactSensor", title: "Select Door/Contact", required: false, multiple: true
}
section("Send Notifications by Text or use below option?") {
input("recipients", "contact", title: "Send notifications to"){
input "phone", "phone", title: "Phone Number (optional, text charges may apply)",
description: "Phone Number", required: false
}
}
section("If combine with 'For ST' android app, this will add features of voice notifications.") {
input "sendPush", "bool", required: false,
title: "Phone/Tablet Auto Notification (Must be set to On =>>)"
}
}
/* Presense */
section("Phone Presence Auto Notifications - For Voice notifications install 'FOR ST' in Google Play") {
input "presence", "capability.presenceSensor", title: "Select Phone/Tablet to Detect: (mandatory & rest below are optional)", required: false, multiple: true
}
def installed() {
initialize()
/* Presense */
subscribe(door, "contact.open", doorOpenHandler)
subscribe(door, "contact.closed", doorClosedHandler)
subscribe(presence, "presence", myHandler)
subscribe(presence, "presence", presenceHandler)
}
def updated() {
initialize()
}
def initialize() {
subscribe(door, "contact.open", doorOpenHandler)
subscribe(door, "contact.closed", doorClosedHandler)
subscribe(presence, "presence", myHandler)
subscribe(presence, "presence", presenceHandler)
}
def doorOpenHandler(evt) {
def message = "EyXAr Detected the ${evt.displayName} is ${evt.value}!"
if (sendPush) {
sendPush(message)
}
if (phone) {
sendSms(phone, message)
}
}
def doorClosedHandler(evt) {
def message = "EyXAr Detected the ${evt.displayName} is ${evt.value}!"
if (sendPush) {
sendPush(message)
}
if (phone) {
sendSms(phone, message)
}
}
def contactHandler(evt) {
if("open" == evt.value)
// contact was opened, turn on a light maybe?
log.debug "Contact is in ${evt.value} state"
if("closed" == evt.value)
// contact was closed, turn off the light?
log.debug "Contact is in ${evt.value} state"
}
/* Presense */
def myHandler(evt) {
if("present" == evt.value)
def message = "EyXAr Detected ${evt.displayName} is ${evt.value}!"
if (sendPush) {
sendPush(message)
}
if (phone) {
sendSms(phone, message)
}
}
def presenceHandler(evt) {
if (evt.value == "present") {
log.debug "EyXAr ${evt.displayName} has arrived at the ${location}!"
sendPush("EyXAr ${evt.displayName} has arrived at the ${location}!")
} else if (evt.value == "not present") {
log.debug "EyXAr ${evt.displayName} has left the ${location}!"
sendPush("EyXAr ${evt.displayName} has left the ${location}!")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
/**
* Simple Sync Connect
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Sync Connect",
namespace: "roomieremote-raconnect",
author: "Roomie Remote, Inc.",
description: "Integrate SmartThings with your Simple Control activities via Simple Sync.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences()
{
page(name: "mainPage", title: "Simple Sync Setup", content: "mainPage", refreshTimeout: 5)
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
page(name:"manualAgentEntry")
page(name:"verifyManualEntry")
}
def mainPage()
{
if (canInstallLabs())
{
return agentDiscovery()
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade")
{
paragraph "$upgradeNeeded"
}
}
}
}
def agentDiscovery(params=[:])
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 5
if (!state.subscribe)
{
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//ssdp request every fifth refresh
if ((refreshCount % 5) == 0)
{
discoverAgents()
}
def agentsDiscovered = agentsDiscovered()
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Pair with Simple Sync")
{
input "selectedAgent", "enum", required:true, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
href(name:"manualAgentEntry",
title:"Manually Configure Simple Sync",
required:false,
page:"manualAgentEntry")
}
}
}
def manualAgentEntry()
{
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
section("Manually Configure Simple Sync")
{
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
}
}
}
def verifyManualEntry()
{
def hexIP = convertIPToHexString(manualIPAddress)
def hexPort = convertToHexString(47147)
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
def hubId = ""
for (hub in location.hubs)
{
if (hub.localIP != null)
{
hubId = hub.id
break
}
}
def manualAgent = [deviceType: "04",
mac: "unknown",
ip: hexIP,
port: hexPort,
ssdpPath: "/upnp/Roomie.xml",
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
hub: hubId,
verified: true,
name: "Simple Sync $manualIPAddress"]
state.agents[uuid] = manualAgent
addOrUpdateAgent(state.agents[uuid])
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
section("")
{
paragraph("Tap Done to complete the installation process.")
}
}
}
def discoverAgents()
{
def urn = getURN()
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
}
def agentsDiscovered()
{
def gAgents = getAgents()
def agents = gAgents.findAll { it?.value?.verified == true }
def map = [:]
agents.each
{
map["${it.value.uuid}"] = it.value.name
}
map
}
def getAgents()
{
if (!state.agents)
{
state.agents = [:]
}
state.agents
}
def installed()
{
initialize()
}
def updated()
{
initialize()
}
def initialize()
{
if (state.subscribe)
{
unsubscribe()
state.subscribe = false
}
if (selectedAgent)
{
addOrUpdateAgent(state.agents[selectedAgent])
}
}
def addOrUpdateAgent(agent)
{
def children = getChildDevices()
def dni = agent.ip + ":" + agent.port
def found = false
children.each
{
if ((it.getDeviceDataByName("mac") == agent.mac))
{
found = true
if (it.getDeviceNetworkId() != dni)
{
it.setDeviceNetworkId(dni)
}
}
else if (it.getDeviceNetworkId() == dni)
{
found = true
}
}
if (!found)
{
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
}
}
def locationHandler(evt)
{
def description = evt?.description
def urn = getURN()
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent?.putAt("hub", hub)
//SSDP DISCOVERY EVENTS
if (parsedEvent?.ssdpTerm?.contains(urn))
{
def agent = parsedEvent
def ip = convertHexToIP(agent.ip)
def agents = getAgents()
agent.verified = true
agent.name = "Simple Sync $ip"
if (!agents[agent.uuid])
{
state.agents[agent.uuid] = agent
}
}
}
private def parseEventMessage(String description)
{
def event = [:]
def parts = description.split(',')
parts.each
{ part ->
part = part.trim()
if (part.startsWith('devicetype:'))
{
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:'))
{
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString)
{
event.ssdpUSN = valueString
def uuid = getUUIDFromUSN(valueString)
if (uuid)
{
event.uuid = uuid
}
}
}
else if (part.startsWith('ssdpTerm:'))
{
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString)
{
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers'))
{
part -= "headers:"
def valueString = part.trim()
if (valueString)
{
event.headers = valueString
}
}
else if (part.startsWith('body'))
{
part -= "body:"
def valueString = part.trim()
if (valueString)
{
event.body = valueString
}
}
}
event
}
def getURN()
{
return "urn:roomieremote-com:device:roomie:1"
}
def getUUIDFromUSN(usn)
{
def parts = usn.split(":")
for (int i = 0; i < parts.size(); ++i)
{
if (parts[i] == "uuid")
{
return parts[i + 1]
}
}
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}
def String convertToHexString(n)
{
String hex = String.format("%X", n.toInteger())
}
def String convertIPToHexString(ipString)
{
String hex = ipString.tokenize(".").collect {
String.format("%02X", it.toInteger())
}.join()
}
def Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
def Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
def List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -0,0 +1,296 @@
/**
* Simple Sync Trigger
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Sync Trigger",
namespace: "roomieremote-ratrigger",
author: "Roomie Remote, Inc.",
description: "Trigger Simple Control activities when certain actions take place in your home.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences {
page(name: "agentSelection", title: "Select your Simple Sync")
page(name: "refreshActivities", title: "Updating list of Simple Sync activities")
page(name: "control", title: "Run a Simple Control activity when something happens")
page(name: "timeIntervalInput", title: "Only during a certain time", install: true, uninstall: true) {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def agentSelection()
{
if (agent)
{
state.refreshCount = 0
}
dynamicPage(name: "agentSelection", title: "Select your Simple Sync", nextPage: "control", install: false, uninstall: true) {
section {
input "agent", "capability.mediaController", title: "Simple Sync", required: true, multiple: false
}
}
}
def control()
{
def activities = agent.latestValue('activities')
if (!activities || !state.refreshCount)
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 4
// Request activities every 5th attempt
if((refreshCount % 5) == 0)
{
agent.getAllActivities()
}
dynamicPage(name: "control", title: "Updating list of Simple Control activities", nextPage: "", refreshInterval: refreshInterval, install: false, uninstall: true) {
section("") {
paragraph "Retrieving activities from Simple Sync"
}
}
}
else
{
dynamicPage(name: "control", title: "Run a Simple Control activity when something happens", nextPage: "timeIntervalInput", install: false, uninstall: true) {
def anythingSet = anythingSet()
if (anythingSet) {
section("When..."){
ifSet "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true
ifSet "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
}
section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
ifUnset "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section("Run this activity"){
input "activity", "enum", title: "Activity?", required: true, options: new groovy.json.JsonSlurper().parseText(activities ?: "[]").activities?.collect { ["${it.uuid}": it.name] }
}
section("More options", hideable: true, hidden: true) {
input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
}
section([mobileOnly:true]) {
label title: "Assign a name", required: false
mode title: "Set for specific mode(s)"
}
}
}
}
private anythingSet() {
for (name in ["motion","motionInactive","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","button1","triggerModes","timeOfDay"]) {
if (settings[name]) {
return true
}
}
return false
}
private ifUnset(Map options, String name, String capability) {
if (!settings[name]) {
input(options, name, capability)
}
}
private ifSet(Map options, String name, String capability) {
if (settings[name]) {
input(options, name, capability)
}
}
def installed() {
subscribeToEvents()
}
def updated() {
unsubscribe()
unschedule()
subscribeToEvents()
}
def subscribeToEvents() {
log.trace "subscribeToEvents()"
subscribe(app, appTouchHandler)
subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler)
subscribe(motionInactive, "motion.inactive", eventHandler)
subscribe(mySwitch, "switch.on", eventHandler)
subscribe(mySwitchOff, "switch.off", eventHandler)
subscribe(arrivalPresence, "presence.present", eventHandler)
subscribe(departurePresence, "presence.not present", eventHandler)
subscribe(button1, "button.pushed", eventHandler)
if (triggerModes) {
subscribe(location, modeChangeHandler)
}
if (timeOfDay) {
schedule(timeOfDay, scheduledTimeHandler)
}
}
def eventHandler(evt) {
if (allOk) {
def lastTime = state[frequencyKey(evt)]
if (oncePerDayOk(lastTime)) {
if (frequency) {
if (lastTime == null || now() - lastTime >= frequency * 60000) {
startActivity(evt)
}
else {
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
}
}
else {
startActivity(evt)
}
}
else {
log.debug "Not taking action because it was already taken today"
}
}
}
def modeChangeHandler(evt) {
log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
if (evt.value in triggerModes) {
eventHandler(evt)
}
}
def scheduledTimeHandler() {
eventHandler(null)
}
def appTouchHandler(evt) {
startActivity(evt)
}
private startActivity(evt) {
agent.startActivity(activity)
if (frequency) {
state.lastActionTimeStamp = now()
}
}
private frequencyKey(evt) {
//evt.deviceId ?: evt.value
"lastActionTimeStamp"
}
private dayString(Date date) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
df.format(date)
}
private oncePerDayOk(Long lastTime) {
def result = true
if (oncePerDay) {
result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
log.trace "oncePerDayOk = $result"
}
result
}
// TODO - centralize somehow
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private timeIntervalLabel()
{
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}

View File

@@ -0,0 +1,676 @@
/**
* Simple Control
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Control",
namespace: "roomieremote-roomieconnect",
author: "Roomie Remote, Inc.",
description: "Integrate SmartThings with your Simple Control activities.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences()
{
section("Allow Simple Control to Monitor and Control These Things...")
{
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
}
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
page(name:"manualAgentEntry")
page(name:"verifyManualEntry")
}
mappings {
path("/devices") {
action: [
GET: "getDevices",
POST: "handleDevicesWithIDs"
]
}
path("/device/:id") {
action: [
GET: "getDevice",
POST: "updateDevice"
]
}
path("/subscriptions") {
action: [
GET: "listSubscriptions",
POST: "addSubscription", // {"deviceId":"xxx", "attributeName":"xxx","callbackUrl":"http://..."}
DELETE: "removeAllSubscriptions"
]
}
path("/subscriptions/:id") {
action: [
DELETE: "removeSubscription"
]
}
}
private getAllDevices()
{
//log.debug("getAllDevices()")
([] + switches + locks + thermostats + imageCaptures + relaySwitches + doorControls + colorControls + musicPlayers + speechSynthesizers + switchLevels + indicators + mediaControllers + tones + tvs + alarms + valves + motionSensors + presenceSensors + beacons + pushButtons + smokeDetectors + coDetectors + contactSensors + accelerationSensors + energyMeters + powerMeters + lightSensors + humiditySensors + temperatureSensors + speechRecognizers + stepSensors + touchSensors)?.findAll()?.unique { it.id }
}
def getDevices()
{
//log.debug("getDevices, params: ${params}")
allDevices.collect {
//log.debug("device: ${it}")
deviceItem(it)
}
}
def getDevice()
{
//log.debug("getDevice, params: ${params}")
def device = allDevices.find { it.id == params.id }
if (!device)
{
render status: 404, data: '{"msg": "Device not found"}'
}
else
{
deviceItem(device)
}
}
def handleDevicesWithIDs()
{
//log.debug("handleDevicesWithIDs, params: ${params}")
def data = request.JSON
def ids = data?.ids?.findAll()?.unique()
//log.debug("ids: ${ids}")
def command = data?.command
def arguments = data?.arguments
if (command)
{
def success = false
//log.debug("command ${command}, arguments ${arguments}")
for (devId in ids)
{
def device = allDevices.find { it.id == devId }
if (device) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
success = true
} else {
//log.debug("device not found ${devId}")
}
}
if (success)
{
render status: 200, data: "{}"
}
else
{
render status: 404, data: '{"msg": "Device not found"}'
}
}
else
{
ids.collect {
def currentId = it
def device = allDevices.find { it.id == currentId }
if (device)
{
deviceItem(device)
}
}
}
}
private deviceItem(device) {
[
id: device.id,
label: device.displayName,
currentState: device.currentStates,
capabilities: device.capabilities?.collect {[
name: it.name
]},
attributes: device.supportedAttributes?.collect {[
name: it.name,
dataType: it.dataType,
values: it.values
]},
commands: device.supportedCommands?.collect {[
name: it.name,
arguments: it.arguments
]},
type: [
name: device.typeName,
author: device.typeAuthor
]
]
}
def updateDevice()
{
//log.debug("updateDevice, params: ${params}")
def data = request.JSON
def command = data?.command
def arguments = data?.arguments
//log.debug("updateDevice, params: ${params}, request: ${data}")
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def device = allDevices.find { it.id == params.id }
if (device) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
}
}
def listSubscriptions()
{
//log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.deviceId }?.collect {
def deviceInfo = state[it.deviceId]
def response = [
id: it.id,
deviceId: it.deviceId,
attributeName: it.data,
handler: it.handler
]
//if (!selectedAgent) {
response.callbackUrl = deviceInfo?.callbackUrl
//}
response
} ?: []
}
def addSubscription() {
def data = request.JSON
def attribute = data.attributeName
def callbackUrl = data.callbackUrl
//log.debug "addSubscription, params: ${params}, request: ${data}"
if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}'
} else {
def device = allDevices.find { it.id == data.deviceId }
if (device) {
//if (!selectedAgent) {
//log.debug "Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl]
//}
//log.debug "Adding subscription"
def subscription = subscribe(device, attribute, deviceHandler)
if (!subscription || !subscription.eventSubscription) {
//log.debug("subscriptions: ${app.subscriptions}")
//for (sub in app.subscriptions)
//{
//log.debug("subscription.id ${sub.id} subscription.handler ${sub.handler} subscription.deviceId ${sub.deviceId}")
//log.debug(sub.properties.collect{it}.join('\n'))
//}
subscription = app.subscriptions?.find { it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
}
def response = [
id: subscription.id,
deviceId: subscription.device?.id,
attributeName: subscription.data,
handler: subscription.handler
]
//if (!selectedAgent) {
response.callbackUrl = callbackUrl
//}
response
} else {
render status: 400, data: '{"msg": "Device not found"}'
}
}
}
def removeSubscription()
{
def subscription = app.subscriptions?.find { it.id == params.id }
def device = subscription?.device
//log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
if (device) {
//log.debug "Removing subscription for device: ${device.id}"
state.remove(device.id)
unsubscribe(device)
}
render status: 204, data: "{}"
}
def removeAllSubscriptions()
{
for (sub in app.subscriptions)
{
//log.debug("Subscription: ${sub}")
//log.debug(sub.properties.collect{it}.join('\n'))
def handler = sub.handler
def device = sub.device
if (device && handler == 'deviceHandler')
{
//log.debug(device.properties.collect{it}.join('\n'))
//log.debug("Removing subscription for device: ${device}")
state.remove(device.id)
unsubscribe(device)
}
}
}
def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId]
//if (selectedAgent) {
// sendToRoomie(evt, agentCallbackUrl)
//} else if (deviceInfo) {
if (deviceInfo)
{
if (deviceInfo.callbackUrl) {
sendToRoomie(evt, deviceInfo.callbackUrl)
} else {
log.warn "No callbackUrl set for device: ${evt.deviceId}"
}
} else {
log.warn "No subscribed device found for device: ${evt.deviceId}"
}
}
def sendToRoomie(evt, String callbackUrl) {
def callback = new URI(callbackUrl)
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
sendHubCommand(new physicalgraph.device.HubAction(
method: "POST",
path: path,
headers: [
"Host": host,
"Content-Type": "application/json"
],
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
))
}
def mainPage()
{
if (canInstallLabs())
{
return agentDiscovery()
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade")
{
paragraph "$upgradeNeeded"
}
}
}
}
def agentDiscovery(params=[:])
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 5
if (!state.subscribe)
{
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//ssdp request every fifth refresh
if ((refreshCount % 5) == 0)
{
discoverAgents()
}
def agentsDiscovered = agentsDiscovered()
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Pair with Simple Sync")
{
input "selectedAgent", "enum", required:false, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
href(name:"manualAgentEntry",
title:"Manually Configure Simple Sync",
required:false,
page:"manualAgentEntry")
}
section("Allow Simple Control to Monitor and Control These Things...")
{
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
}
}
}
def manualAgentEntry()
{
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
section("Manually Configure Simple Sync")
{
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
}
}
}
def verifyManualEntry()
{
def hexIP = convertIPToHexString(manualIPAddress)
def hexPort = convertToHexString(47147)
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
def hubId = ""
for (hub in location.hubs)
{
if (hub.localIP != null)
{
hubId = hub.id
break
}
}
def manualAgent = [deviceType: "04",
mac: "unknown",
ip: hexIP,
port: hexPort,
ssdpPath: "/upnp/Roomie.xml",
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
hub: hubId,
verified: true,
name: "Simple Sync $manualIPAddress"]
state.agents[uuid] = manualAgent
addOrUpdateAgent(state.agents[uuid])
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
section("")
{
paragraph("Tap Done to complete the installation process.")
}
}
}
def discoverAgents()
{
def urn = getURN()
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
}
def agentsDiscovered()
{
def gAgents = getAgents()
def agents = gAgents.findAll { it?.value?.verified == true }
def map = [:]
agents.each
{
map["${it.value.uuid}"] = it.value.name
}
map
}
def getAgents()
{
if (!state.agents)
{
state.agents = [:]
}
state.agents
}
def installed()
{
initialize()
}
def updated()
{
initialize()
}
def initialize()
{
if (state.subscribe)
{
unsubscribe()
state.subscribe = false
}
if (selectedAgent)
{
addOrUpdateAgent(state.agents[selectedAgent])
}
}
def addOrUpdateAgent(agent)
{
def children = getChildDevices()
def dni = agent.ip + ":" + agent.port
def found = false
children.each
{
if ((it.getDeviceDataByName("mac") == agent.mac))
{
found = true
if (it.getDeviceNetworkId() != dni)
{
it.setDeviceNetworkId(dni)
}
}
else if (it.getDeviceNetworkId() == dni)
{
found = true
}
}
if (!found)
{
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
}
}
def locationHandler(evt)
{
def description = evt?.description
def urn = getURN()
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent?.putAt("hub", hub)
//SSDP DISCOVERY EVENTS
if (parsedEvent?.ssdpTerm?.contains(urn))
{
def agent = parsedEvent
def ip = convertHexToIP(agent.ip)
def agents = getAgents()
agent.verified = true
agent.name = "Simple Sync $ip"
if (!agents[agent.uuid])
{
state.agents[agent.uuid] = agent
}
}
}
private def parseEventMessage(String description)
{
def event = [:]
def parts = description.split(',')
parts.each
{ part ->
part = part.trim()
if (part.startsWith('devicetype:'))
{
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:'))
{
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString)
{
event.ssdpUSN = valueString
def uuid = getUUIDFromUSN(valueString)
if (uuid)
{
event.uuid = uuid
}
}
}
else if (part.startsWith('ssdpTerm:'))
{
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString)
{
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers'))
{
part -= "headers:"
def valueString = part.trim()
if (valueString)
{
event.headers = valueString
}
}
else if (part.startsWith('body'))
{
part -= "body:"
def valueString = part.trim()
if (valueString)
{
event.body = valueString
}
}
}
event
}
def getURN()
{
return "urn:roomieremote-com:device:roomie:1"
}
def getUUIDFromUSN(usn)
{
def parts = usn.split(":")
for (int i = 0; i < parts.size(); ++i)
{
if (parts[i] == "uuid")
{
return parts[i + 1]
}
}
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}
def String convertToHexString(n)
{
String hex = String.format("%X", n.toInteger())
}
def String convertIPToHexString(ipString)
{
String hex = ipString.tokenize(".").collect {
String.format("%02X", it.toInteger())
}.join()
}
def Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
def Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
def List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -353,7 +353,7 @@ def onLocation(evt) {
}
else if (
lanEvent.headers && lanEvent.body &&
lanEvent.headers."content-type".contains("xml")
lanEvent.headers."content-type"?.contains("xml")
)
{
def parsers = getParsers()

View File

@@ -28,7 +28,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
singleInstance: true
singleInstance: true
) {
appSetting "clientId"
}
@@ -61,7 +61,7 @@ def authPage() {
description = "Click to enter Ecobee Credentials"
}
def redirectUrl = buildRedirectUrl //"${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}"
def redirectUrl = buildRedirectUrl
log.debug "RedirectUrl = ${redirectUrl}"
// get rid of next button until the user is actually auth'd
if (!oauthTokenProvided) {
@@ -103,7 +103,7 @@ def oauthInitUrl() {
scope: "smartRead,smartWrite",
client_id: smartThingsClientId,
state: atomicState.oauthInitState,
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
redirect_uri: callbackUrl
]
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
@@ -115,14 +115,13 @@ def callback() {
def code = params.code
def oauthState = params.state
//verify oauthState == atomicState.oauthInitState, so the callback corresponds to the authentication request
if (oauthState == atomicState.oauthInitState){
def tokenParams = [
grant_type: "authorization_code",
code : code,
client_id : smartThingsClientId,
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
redirect_uri: callbackUrl
]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -236,6 +235,7 @@ def connectionStatus(message, redirectUrl = null) {
def getEcobeeThermostats() {
log.debug "getting device list"
atomicState.remoteSensors = []
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
@@ -247,31 +247,26 @@ def getEcobeeThermostats() {
]
def stats = [:]
try {
httpGet(deviceListParams) { resp ->
try {
httpGet(deviceListParams) { resp ->
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors
def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat)
}
} else {
log.debug "http status: ${resp.status}"
//refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) {
log.debug "Storing the failed action to try later"
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
} else {
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat)
}
} else {
log.debug "http status: ${resp.status}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
} catch(Exception e) {
log.debug "___exception getEcobeeThermostats(): " + e
refreshAuthToken()
}
atomicState.thermostats = stats
return stats
@@ -279,11 +274,14 @@ def getEcobeeThermostats() {
Map sensorsDiscovered() {
def map = [:]
atomicState.remoteSensors.each {
if (it.type != "thermostat") {
def value = "${it?.name}"
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
map["${key}"] = value
log.info "list ${atomicState.remoteSensors}"
atomicState.remoteSensors.each { sensors ->
sensors.each {
if (it.type != "thermostat") {
def value = "${it?.name}"
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
map["${key}"] = value
}
}
}
atomicState.sensors = map
@@ -317,7 +315,7 @@ def initialize() {
def devices = thermostats.collect { dni ->
def d = getChildDevice(dni)
if(!d) {
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"Ecobee Thermostat:${atomicState.thermostats[dni]}"])
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"${atomicState.thermostats[dni]}" ?: "Ecobee Thermostat"])
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -328,7 +326,7 @@ def initialize() {
def sensors = ecobeesensors.collect { dni ->
def d = getChildDevice(dni)
if(!d) {
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"Ecobee Sensor:${atomicState.sensors[dni]}"])
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"${atomicState.sensors[dni]}" ?:"Ecobee Sensor"])
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -354,21 +352,17 @@ def initialize() {
atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
state.timeSendPush = null
//send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = null
atomicState.reAttempt = 0
pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins
runEvery5Minutes("poll")
//since access_token expires every 2 hours
runEvery1Hour("refreshAuthToken")
atomicState.reAttempt = 0
}
def pollHandler() {
@@ -389,18 +383,10 @@ def pollHandler() {
def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def data = ""
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
def result = false
// // TODO: test this:
//
// def jsonRequestBody = toJson([
// selection:[
// selectionType: "thermostats",
// selectionMatch: getChildDeviceIdsString(),
// includeRuntime: true
// ]
// ])
def pollParams = [
uri: apiEndpoint,
@@ -411,11 +397,6 @@ def pollChildren(child = null) {
try{
httpGet(pollParams) { resp ->
// if (resp.data) {
// debugEventFromParent(child, "pollChildren(child) >> resp.status = ${resp.status}, resp.data = ${resp.data}")
// }
if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
@@ -426,77 +407,88 @@ def pollChildren(child = null) {
log.debug "updating dni $dni"
def data = [
data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: stat.runtime.actualTemperature / 10,
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
data["temperature"] = data["temperature"] ? data["temperature"].toDouble().toInteger() : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? data["heatingSetpoint"].toDouble().toInteger() : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? data["coolingSetpoint"].toDouble().toInteger() : data["coolingSetpoint"]
// debugEventFromParent(child, "Event Data = ${data}")
if (location.temperatureScale == "F")
{
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data]
return collector
}
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
} else {
log.error "polling children & got http status ${resp.status}"
//refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) {
log.debug "Storing the failed action to try later"
atomicState.action = "pollChildren";
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
else {
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
}
} catch(Exception e) {
log.debug "___exception polling children: " + e
// debugEventFromParent(child, "___exception polling children: " + e)
refreshAuthToken()
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
}
return result
}
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild(child){
def pollChild(){
if (pollChildren(child)){
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
// debugEventFromParent(child, "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}") //TODO comment
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent
// return tData.data
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
// debugEventFromParent(child, "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling") //TODO comment
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
def devices = getChildDevices()
if (pollChildren()){
devices.each { child ->
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
}
}
} else {
// debugEventFromParent(child, "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling") //TODO comment
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
log.info "ERROR: pollChildren()"
return null
}
}
void poll() {
def devices = getChildDevices()
devices.each {pollChild(it)}
pollChild()
}
def availableModes(child) {
@@ -513,9 +505,6 @@ def availableModes(child) {
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null
}
@@ -542,8 +531,6 @@ def currentMode(child) {
if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null
}
@@ -561,8 +548,17 @@ def updateSensorData() {
def occupancy = ""
it.capability.each {
if (it.type == "temperature") {
temperature = it.value as Double
temperature = (temperature / 10).toInteger()
if (it.value == "unknown") {
temperature = "--"
} else {
if (location.temperatureScale == "F") {
temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
}
} else if (it.type == "occupancy") {
if(it.value == "true")
occupancy = "active"
@@ -575,7 +571,6 @@ def updateSensorData() {
if(d) {
d.sendEvent(name:"temperature", value: temperature)
d.sendEvent(name:"motion", value: occupancy)
// debugEventFromParent(d, "temperature : ${temperature}, motion:${occupancy}")
}
}
}
@@ -595,68 +590,65 @@ def toQueryString(Map m) {
}
private refreshAuthToken() {
log.debug "refreshing auth token"
log.debug "refreshing auth token"
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
log.debug refreshParams
log.debug refreshParams
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
debugEvent("Token refreshed ... calling saved RestAction now!")
log.debug resp
log.debug resp
jsonMap = resp.data
jsonMap = resp.data
if(resp.data) {
if(resp.data) {
log.debug resp.data
debugEvent("Response = ${resp.data}")
log.debug resp.data
debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"()
"${atomicState.action}"()
//remove saved action
atomicState.action = ""
}
atomicState.action = ""
}
}
atomicState.action = ""
} else {
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
}
}
} catch(Exception e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
}
atomicState.action = ""
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken")
} else if (e.statusCode == 401) { //refresh token is expired
} else if (e.statusCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
@@ -665,20 +657,16 @@ private refreshAuthToken() {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
}
}
}
}
}
}
def resumeProgram(child, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "resumeProgram children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
//, { "type": "sendMessage", "params": { "text": "Setpoint Updated" } }
def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "resumeProgram(child) with result ${result}")
return result
}
@@ -686,28 +674,27 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10
int c = cooling * 10
// log.debug "setpoints____________ - h: $heating - $h, c: $cooling - $c"
// def thermostatIdsString = getChildDeviceIdsString()
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
// def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}, { "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": "indefinite" } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
}
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) {
int h = heating * 10
int c = cooling * 10
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
def result = sendJson(child, jsonRequestBody)
// debugEventFromParent(child, "setHold: heating: ${h}, cooling: ${c} with result ${result}")
return result
}
def setMode(child, mode, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "setCoolingSetpoint children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
// log.debug "Mode Request Body = ${jsonRequestBody}"
// debugEvent ("Mode Request Body = ${jsonRequestBody}")
def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "setMode to ${mode} with result ${result}")
return result
}
@@ -724,8 +711,6 @@ def sendJson(child = null, String jsonBody) {
try{
httpPost(cmdParams) { resp ->
// debugEventFromParent(child, "sendJson >> resp.status ${resp.status}, resp.data: ${resp.data}")
if(resp.status == 200) {
log.debug "updated ${resp.data}"
@@ -736,30 +721,21 @@ def sendJson(child = null, String jsonBody) {
log.debug "Error return code = ${resp.data.status.code}"
debugEvent("Error return code = ${resp.data.status.code}")
}
} else {
log.error "sent Json & got http status ${resp.status} - ${resp.status.code}"
debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}")
//refresh the auth token
if (resp.status == 500 && resp.status.code == 14) {
//log.debug "Storing the failed action to try later"
log.debug "Refreshing your auth_token!"
debugEvent ("Refreshing OAUTH Token")
refreshAuthToken()
return false
} else {
debugEvent ("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
return false
}
}
}
} catch(Exception e) {
log.debug "Exception Sending Json: " + e
debugEvent ("Exception Sending JSON: " + e)
refreshAuthToken()
return false
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
if (returnStatus == 0)
return true
@@ -794,25 +770,37 @@ def debugEventFromParent(child, message) {
//send both push notification and mobile activity feeds
def sendPushAndFeeds(notificationMessage){
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
} else {
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
atomicState.authToken = null
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
} else {
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
atomicState.authToken = null
}
def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices()
devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
}
def devices = getChildDevices()
devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
}
}
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -201,8 +201,8 @@ def completionPage() {
section("Switches") {
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
if (completionSwitches || androidClient()) {
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], style: "segmented", defaultValue: "on")
if (completionSwitches) {
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], defaultValue: "on")
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
}
}

View File

@@ -15,7 +15,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Hue (Connect)",
namespace: "smartthings",
@@ -35,23 +35,11 @@ preferences {
}
def mainPage() {
if(canInstallLabs()) {
def bridges = bridgesDiscovered()
if (state.username && bridges) {
return bulbDiscovery()
} else {
return bridgeDiscovery()
}
def bridges = bridgesDiscovered()
if (state.username && bridges) {
return bulbDiscovery()
} else {
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"bridgeDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade") {
paragraph "$upgradeNeeded"
}
}
return bridgeDiscovery()
}
}
@@ -70,9 +58,9 @@ def bridgeDiscovery(params=[:])
state.bridges = [:]
state.bridgeRefreshCount = 0
app.updateSetting("selectedHue", "")
}
}
subscribe(location, null, locationHandler, [filterEvents:false])
ssdpSubscribe()
//bridge discovery request every 15 //25 seconds
if((bridgeRefreshCount % 5) == 0) {
@@ -80,7 +68,7 @@ def bridgeDiscovery(params=[:])
}
//setup.xml request every 3 seconds except on discoveries
if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) {
verifyHueBridges()
}
@@ -142,8 +130,8 @@ def bulbDiscovery() {
def bulboptions = bulbsDiscovered() ?: [:]
def numFound = bulboptions.size() ?: 0
if (numFound == 0)
app.updateSetting("selectedBulbs", "")
app.updateSetting("selectedBulbs", "")
if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs()
}
@@ -152,7 +140,7 @@ def bulbDiscovery() {
section("Please wait while we discover your Hue Bulbs. 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 Bulbs (${numFound} found)", multiple:true, options:bulboptions
}
section {
section {
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
@@ -164,6 +152,10 @@ private discoverBridges() {
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
}
void ssdpSubscribe() {
subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:basic:1", ssdpBridgeHandler)
}
private sendDeveloperReq() {
def token = app.id
def host = getBridgeIP()
@@ -173,7 +165,7 @@ private sendDeveloperReq() {
headers: [
HOST: host
],
body: [devicetype: "$token-0", username: "$token-0"]], "${selectedHue}"))
body: [devicetype: "$token-0"]], "${selectedHue}", [callback: "usernameHandler"]))
}
private discoverHueBulbs() {
@@ -183,16 +175,17 @@ private discoverHueBulbs() {
path: "/api/${state.username}/lights",
headers: [
HOST: host
]], "${selectedHue}"))
]], "${selectedHue}", [callback: "lightsHandler"]))
}
private verifyHueBridge(String deviceNetworkId, String host) {
log.trace "Verify Hue Bridge $deviceNetworkId"
sendHubCommand(new physicalgraph.device.HubAction([
method: "GET",
path: "/description.xml",
headers: [
HOST: host
]], deviceNetworkId))
]], deviceNetworkId, [callback: "bridgeDescriptionHandler"]))
}
private verifyHueBridges() {
@@ -258,13 +251,13 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unsubscribe()
unschedule()
unsubscribe()
unschedule()
initialize()
}
def initialize() {
log.debug "Initializing"
log.debug "Initializing"
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
@@ -293,24 +286,59 @@ def uninstalled(){
def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:]
if (state.inBulbDiscovery) {
if (state.inBulbDiscovery) {
def logg = ""
log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data)
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub]
}
}
}
def bridge = null
if (selectedHue)
bridge = getChildDevice(selectedHue)
if (selectedHue) {
bridge = getChildDevice(selectedHue)
}
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
msg = "${bulbs.size()} bulbs found. ${bulbs}"
return msg
}
private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType)
// Automatically change users Hue bulbs to correct device types
if (deviceType && !(device?.typeName?.equalsIgnoreCase(deviceType))) {
log.debug "Update device type: \"$device.label\" ${device?.typeName}->$deviceType"
device.setDeviceType(deviceType)
}
}
private getDeviceType(hueType) {
// Determine ST device type based on Hue classification of light
if (hueType?.equalsIgnoreCase("Dimmable light"))
return "Hue Lux Bulb"
else if (hueType?.equalsIgnoreCase("Extended Color Light"))
return "Hue Bulb"
else if (hueType?.equalsIgnoreCase("Color Light"))
return "Hue Bloom"
else
return null
}
private addChildBulb(dni, hueType, name, hub, update=false, device = null) {
def deviceType = getDeviceType(hueType)
if (deviceType) {
return addChildDevice("smartthings", deviceType, dni, hub, ["label": name])
}
else {
log.warn "Device type $hueType not supported"
return null
}
}
def addBulbs() {
def bulbs = getHueBulbs()
selectedBulbs?.each { dni ->
@@ -320,28 +348,24 @@ def addBulbs() {
if (bulbs instanceof java.util.Map) {
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
if (newHueBulb != null) {
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) {
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
} else {
} else {
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
d.refresh()
}
d.refresh()
} else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) {
// Update device type if incorrect
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") && d.typeName == "Hue Bulb") {
d.setDeviceType("Hue Lux Bulb")
}
upgradeDeviceType(d, newHueBulb?.value?.type)
}
}
}
@@ -355,7 +379,7 @@ def addBridge() {
def d = getChildDevice(selectedHue)
if(!d) {
// compatibility with old devices
def newbridge = true
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
@@ -363,12 +387,13 @@ def addBridge() {
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 (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
newbridge = false
}
}
}
}
}
if (newbridge) {
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
@@ -379,13 +404,13 @@ def addBridge() {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
} else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
}
}
} else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
}
}
}
} else {
log.debug "found ${d.displayName} with id $selectedHue already exists"
@@ -393,6 +418,111 @@ def addBridge() {
}
}
def ssdpBridgeHandler(evt) {
def description = evt.description
log.trace "Location: $description"
def hub = evt?.hubId
def parsedEvent = parseLanMessage(description)
parsedEvent << ["hub":hub]
def bridges = getHueBridges()
log.trace bridges.toString()
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
//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
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
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")}"
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()
}
}
}
} else {
if (d.getDeviceDataByName("networkAddress")) {
networkAddress = d.getDeviceDataByName("networkAddress")
} 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)
}
}
}
}
void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
log.trace "description.xml response (application/xml)"
def body = hubResponse.xml
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"
}
}
}
void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json
if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!"
body.each { k, v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
}
}
}
void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json
if (body.success != null) {
if (body.success[0] != null) {
if (body.success[0].username)
state.username = body.success[0].username
}
} else if (body.error != null) {
//TODO: handle retries...
log.error "ERROR: application/json ${body.error}"
}
}
}
/**
* @deprecated This has been replaced by the combination of {@link #ssdpBridgeHandler()}, {@link #bridgeDescriptionHandler()},
* {@link #lightsHandler()}, and {@link #usernameHandler()}. After a pending event subscription migration, it can be removed.
*/
@Deprecated
def locationHandler(evt) {
def description = evt.description
log.trace "Location: $description"
@@ -428,17 +558,19 @@ def locationHandler(evt) {
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)
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
doDeviceSync()
}
}
}
} else {
if (d.getDeviceDataByName("networkAddress"))
networkAddress = d.getDeviceDataByName("networkAddress")
else
networkAddress = d.latestState('networkAddress').stringValue
if (d.getDeviceDataByName("networkAddress")) {
networkAddress = d.getDeviceDataByName("networkAddress")
} 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..."
@@ -447,7 +579,7 @@ def locationHandler(evt) {
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
}
}
}
@@ -466,13 +598,14 @@ def locationHandler(evt) {
log.error "/description.xml returned a bridge that didn't exist"
}
}
} else if(headerString?.contains("json")) {
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
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) {
if (body.success[0].username)
if (body.success[0].username) {
state.username = body.success[0].username
}
}
} else if (body.error != null) {
//TODO: handle retries...
@@ -483,7 +616,7 @@ def locationHandler(evt) {
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!"
body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub]
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
}
}
@@ -497,24 +630,25 @@ def doDeviceSync(){
log.trace "Doing Hue Device Sync!"
convertBulbListToMap()
poll()
try {
subscribe(location, null, locationHandler, [filterEvents:false])
} catch (all) {
log.trace "Subscription already exist"
}
ssdpSubscribe()
discoverBridges()
}
def isValidSource(macAddress) {
def vbridges = getVerifiedHueBridges()
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
}
/////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
def parse(childDevice, description) {
def parsedEvent = parseLanMessage(description)
def parsedEvent = parseLanMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = parsedEvent.headers.toString()
def bodyString = parsedEvent.body.toString()
if (headerString?.contains("json")) {
if (headerString?.contains("json")) {
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
@@ -522,11 +656,11 @@ def parse(childDevice, description) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.HashMap) {
if (body instanceof java.util.HashMap) {
//poll response
def bulbs = getChildDevices()
for (bulb in body) {
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (d) {
if (bulb.value.state?.reachable) {
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
@@ -541,18 +675,18 @@ def parse(childDevice, description) {
}
} else {
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
if (bulb.value.state.sat) {
def hue = 23
def sat = 56
def hex = colorUtil.hslToHex(23, 56)
sendEvent(d.deviceNetworkId, [name: "color", value: hex])
sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
}
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
}
}
}
}
}
}
else
{ //put response
@@ -601,13 +735,27 @@ def parse(childDevice, description) {
}
}
}
}
} else {
log.debug "parse - got something other than headers,body..."
return []
}
}
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 on(childDevice) {
log.debug "Executing 'on'"
put("lights/${getId(childDevice)}/state", [on: true])
@@ -622,7 +770,7 @@ def off(childDevice) {
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def level
def level
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
}
@@ -639,36 +787,62 @@ def setHue(childDevice, percent) {
put("lights/${getId(childDevice)}/state", [hue: level])
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
def alert = huesettings.alert ? huesettings.alert : "none"
def transition = huesettings.transition ? huesettings.transition : 4
def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
if (huesettings.level != null) {
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
value.on = value.bri > 0
}
if (huesettings.switch) {
value.on = huesettings.switch == "on"
}
log.debug "sending command $value"
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654))
def value = [ct: ct, on: true]
log.trace "sending command $value"
put("lights/${getId(childDevice)}/state", value)
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def value = [:]
def hue = null
def sat = null
def xy = null
if (huesettings.hex != null) {
value.xy = getHextoXY(huesettings.hex)
} else {
if (huesettings.hue != null)
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
}
// 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
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transition = huesettings.transition ? huesettings.transition : 4
// Make sure to turn off light if requested
if (huesettings.switch == "off")
value.on = false
log.debug "sending command $value"
put("lights/${getId(childDevice)}/state", value)
return "Color set to $value"
}
def nextLevel(childDevice) {
def level = device.latestValue("level") as Integer ?: 0
if (level < 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(childDevice,level)
def level = device.latestValue("level") as Integer ?: 0
if (level < 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
} else {
level = 25
}
setLevel(childDevice,level)
}
private getId(childDevice) {
@@ -683,19 +857,15 @@ private getId(childDevice) {
private poll() {
def host = getBridgeIP()
def uri = "/api/${state.username}/lights/"
try {
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue))
} catch (all) {
log.warn "Parsing Body failed - trying again..."
doDeviceSync()
}
}
private put(path, body) {
def host = getBridgeIP()
def host = getBridgeIP()
def uri = "/api/${state.username}/$path"
def bodyJSON = new groovy.json.JsonBuilder(body).toString()
def length = bodyJSON.getBytes().size().toString()
@@ -721,11 +891,11 @@ private getBridgeIP() {
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) {
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
@@ -735,12 +905,63 @@ private getBridgeIP() {
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 getHextoXY(String colorStr) {
// For the hue bulb the corners of the triangle are:
// -Red: 0.675, 0.322
// -Green: 0.4091, 0.518
// -Blue: 0.167, 0.04
def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
double[] normalizedToOne = new double[3];
normalizedToOne[0] = (cred / 255);
normalizedToOne[1] = (cgreen / 255);
normalizedToOne[2] = (cblue / 255);
float red, green, blue;
// Make red more vivid
if (normalizedToOne[0] > 0.04045) {
red = (float) Math.pow(
(normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
} else {
red = (float) (normalizedToOne[0] / 12.92);
}
// Make green more vivid
if (normalizedToOne[1] > 0.04045) {
green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
} else {
green = (float) (normalizedToOne[1] / 12.92);
}
// Make blue more vivid
if (normalizedToOne[2] > 0.04045) {
blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
} else {
blue = (float) (normalizedToOne[2] / 12.92);
}
float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
float x = (X != 0 ? X / (X + Y + Z) : 0);
float y = (Y != 0 ? Y / (X + Y + Z) : 0);
double[] xy = new double[2];
xy[0] = x;
xy[1] = y;
return xy;
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
@@ -750,7 +971,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) {
def map = [:]
state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "hub":bulb.hub]]
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]]
}
state.bulbs = map
}
@@ -764,14 +985,10 @@ private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}

View File

@@ -89,7 +89,7 @@ mappings {
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getServercallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
def authPage() {
@@ -166,7 +166,7 @@ def callback() {
def init() {
log.debug "Requesting Code"
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ]
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
}
@@ -419,9 +419,11 @@ def addDevice() {
def d = getChildDevice(dni)
if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni }
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni"
poll()
if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni"
poll()
}
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}

View File

@@ -0,0 +1,31 @@
'''Acceleration Detected'''.ko=가속이 감지되었을 때
'''Arrival Of'''.ko=도착했을 때
'''Both Push and SMS?'''.ko=푸시 알람과 SMS 모두 사용
'''Button Pushed'''.ko=버튼이 눌렸을 때
'''Contact Closes'''.ko=닫힘이 감지되었을 때
'''Contact Opens'''.ko=열림이 감지되었을 때
'''Departure Of'''.ko=출발할 때
'''Message Text'''.ko=문자 메시지
'''Minutes'''.ko=메시지 전송 간격(분)
'''Motion Here'''.ko=움직임이 감지되었을 때
'''Phone Number (for SMS, optional)'''.ko=전화번호 (옵션)
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
'''Smoke Detected'''.ko=연기가 감지되었을 때
'''Switch Turned Off'''.ko=스위치가 꺼졌을 때
'''Switch Turned On'''.ko=스위치가 켜졌을 때
'''Choose one or more, when...'''.ko=다음 상황 중 하나 이상 선택
'''Yes'''.ko=
'''No'''.ko=아니요
'''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 작성 (작성하지 않을 경우 디폴트 메시지 전송)
'''Via a push notification and/or an SMS message'''.ko=푸시 알람 및 SMS 설정
'''Set for specific mode(s)'''.ko=특정 상태 설정
'''Tap to set'''.ko=눌러서 설정
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지 전송 간격 설정
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 국가에 거주한다면 국가 코드와 함께 입력하여 주세요.
'''Water Sensor Wet'''.ko=누수가 감지되었을 때
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ location.name }}에 {{ triggerEvent.linkText }} 귀가
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ location.name }}에 {{ triggerEvent.linkText }} 귀가
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ location.name }}에서 {{ triggerEvent.linkText }} 외출
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ location.name }}에서 {{ triggerEvent.linkText }} 외출
'''Assign a name'''.ko=이름 설정
'''Choose Modes'''.ko=상태 선택

View File

@@ -20,19 +20,19 @@
* 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell.
*/
definition(
name: "Notify Me When",
namespace: "smartthings",
author: "SmartThings",
description: "Receive notifications when anything happens in your home.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
name: "Notify Me When",
namespace: "smartthings",
author: "SmartThings",
description: "Receive notifications when anything happens in your home.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
)
preferences {
section("Choose one or more, when..."){
input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
@@ -47,11 +47,11 @@ preferences {
input "messageText", "text", title: "Message Text", required: false
}
section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
}
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
}
}
section("Minimum time between messages (optional, defaults to every message)") {
input "frequency", "decimal", title: "Minutes", required: false
@@ -71,7 +71,7 @@ def updated() {
def subscribeToEvents() {
subscribe(button, "button.pushed", eventHandler) //tw
subscribe(contact, "contact.open", eventHandler)
subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler)
@@ -99,49 +99,55 @@ def eventHandler(evt) {
}
private sendMessage(evt) {
def msg = messageText ?: defaultText(evt)
String msg = messageText
Map options = [:]
if (!messageText) {
msg = defaultText(evt)
options = [translatable: true, triggerEvent: evt]
}
log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'"
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients)
}
else {
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options)
} else {
if (!phone || pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
//sendPush(msg)
}
if (phone) {
options.phone = phone
log.debug 'sending SMS'
//sendSms(phone, msg)
}
sendNotification(msg, options)
}
if (!phone || pushAndPhone != "No") {
log.debug "sending push"
sendPush(msg)
}
if (phone) {
log.debug "sending SMS"
sendSms(phone, msg)
}
}
if (frequency) {
state[evt.deviceId] = now()
}
}
private defaultText(evt) {
if (evt.name == "presence") {
if (evt.value == "present") {
if (evt.name == 'presence') {
if (evt.value == 'present') {
if (includeArticle) {
"$evt.linkText has arrived at the $location.name"
'{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'
}
else {
"$evt.linkText has arrived at $location.name"
'{{ triggerEvent.linkText }} has arrived at {{ location.name }}'
}
}
else {
} else {
if (includeArticle) {
"$evt.linkText has left the $location.name"
'{{ triggerEvent.linkText }} has left the {{ location.name }}'
}
else {
"$evt.linkText has left $location.name"
'{{ triggerEvent.linkText }} has left {{ location.name }}'
}
}
}
else {
evt.descriptionText
} else {
'{{ triggerEvent.descriptionText }}'
}
}

View File

@@ -1,421 +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.
*
* Samsung TV Service Manager
*
* Author: SmartThings (Juan Risso)
*/
definition(
name: "Samsung TV (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png",
singleInstance: true
)
preferences {
page(name:"samsungDiscovery", title:"Samsung TV Setup", content:"samsungDiscovery", refreshTimeout:5)
}
def getDeviceType() {
return "urn:samsung.com:device:RemoteControlReceiver:1"
}
//PAGES
def samsungDiscovery()
{
if(canInstallLabs())
{
int samsungRefreshCount = !state.samsungRefreshCount ? 0 : state.samsungRefreshCount as int
state.samsungRefreshCount = samsungRefreshCount + 1
def refreshInterval = 3
def options = samsungesDiscovered() ?: []
def numFound = options.size() ?: 0
if(!state.subscribe) {
log.trace "subscribe to location"
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//samsung discovery request every 5 //25 seconds
if((samsungRefreshCount % 5) == 0) {
log.trace "Discovering..."
discoversamsunges()
}
//setup.xml request every 3 seconds except on discoveries
if(((samsungRefreshCount % 1) == 0) && ((samsungRefreshCount % 8) != 0)) {
log.trace "Verifing..."
verifysamsungPlayer()
}
return dynamicPage(name:"samsungDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Samsung TV. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedsamsung", "enum", required:false, title:"Select Samsung TV (${numFound} found)", multiple:true, options:options
}
}
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"samsungDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) {
section("Upgrade") {
paragraph "$upgradeNeeded"
}
}
}
}
def installed() {
log.trace "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.trace "Updated with settings: ${settings}"
unschedule()
initialize()
}
def uninstalled() {
def devices = getChildDevices()
log.trace "deleting ${devices.size()} samsung"
devices.each {
deleteChildDevice(it.deviceNetworkId)
}
}
def initialize() {
// remove location subscription afterwards
if (selectedsamsung) {
addsamsung()
}
//Check every 5 minutes for IP change
runEvery5Minutes("discoversamsunges")
}
//CHILD DEVICE METHODS
def addsamsung() {
def players = getVerifiedsamsungPlayer()
log.trace "Adding childs"
selectedsamsung.each { dni ->
def d = getChildDevice(dni)
if(!d) {
def newPlayer = players.find { (it.value.ip + ":" + it.value.port) == dni }
log.trace "newPlayer = $newPlayer"
log.trace "dni = $dni"
d = addChildDevice("smartthings", "Samsung Smart TV", dni, newPlayer?.value.hub, [label:"${newPlayer?.value.name}"])
log.trace "created ${d.displayName} with id $dni"
d.setModel(newPlayer?.value.model)
log.trace "setModel to ${newPlayer?.value.model}"
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}
}
}
private tvAction(key,deviceNetworkId) {
log.debug "Executing ${tvCommand}"
def tvs = getVerifiedsamsungPlayer()
def thetv = tvs.find { (it.value.ip + ":" + it.value.port) == deviceNetworkId }
// Standard Connection Data
def appString = "iphone..iapp.samsung"
def appStringLength = appString.getBytes().size()
def tvAppString = "iphone.UN60ES8000.iapp.samsung"
def tvAppStringLength = tvAppString.getBytes().size()
def remoteName = "SmartThings".encodeAsBase64().toString()
def remoteNameLength = remoteName.getBytes().size()
// Device Connection Data
def ipAddress = convertHexToIP(thetv?.value.ip).encodeAsBase64().toString()
def ipAddressHex = deviceNetworkId.substring(0,8)
def ipAddressLength = ipAddress.getBytes().size()
def macAddress = thetv?.value.mac.encodeAsBase64().toString()
def macAddressLength = macAddress.getBytes().size()
// The Authentication Message
def authenticationMessage = "${(char)0x64}${(char)0x00}${(char)ipAddressLength}${(char)0x00}${ipAddress}${(char)macAddressLength}${(char)0x00}${macAddress}${(char)remoteNameLength}${(char)0x00}${remoteName}"
def authenticationMessageLength = authenticationMessage.getBytes().size()
def authenticationPacket = "${(char)0x00}${(char)appStringLength}${(char)0x00}${appString}${(char)authenticationMessageLength}${(char)0x00}${authenticationMessage}"
// If our initial run, just send the authentication packet so the prompt appears on screen
if (key == "AUTHENTICATE") {
sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8"))
} else {
// Build the command we will send to the Samsung TV
def command = "KEY_${key}".encodeAsBase64().toString()
def commandLength = command.getBytes().size()
def actionMessage = "${(char)0x00}${(char)0x00}${(char)0x00}${(char)commandLength}${(char)0x00}${command}"
def actionMessageLength = actionMessage.getBytes().size()
def actionPacket = "${(char)0x00}${(char)tvAppStringLength}${(char)0x00}${tvAppString}${(char)actionMessageLength}${(char)0x00}${actionMessage}"
// Send both the authentication and action at the same time
sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket + actionPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8"))
}
}
private discoversamsunges()
{
sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN))
}
private verifysamsungPlayer() {
def devices = getsamsungPlayer().findAll { it?.value?.verified != true }
if(devices) {
log.warn "UNVERIFIED PLAYERS!: $devices"
}
devices.each {
verifysamsung((it?.value?.ip + ":" + it?.value?.port), it?.value?.ssdpPath)
}
}
private verifysamsung(String deviceNetworkId, String devicessdpPath) {
log.trace "dni: $deviceNetworkId, ssdpPath: $devicessdpPath"
String ip = getHostAddress(deviceNetworkId)
log.trace "ip:" + ip
sendHubCommand(new physicalgraph.device.HubAction("""GET ${devicessdpPath} HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
}
Map samsungesDiscovered() {
def vsamsunges = getVerifiedsamsungPlayer()
def map = [:]
vsamsunges.each {
def value = "${it.value.name}"
def key = it.value.ip + ":" + it.value.port
map["${key}"] = value
}
log.trace "Devices discovered $map"
map
}
def getsamsungPlayer()
{
state.samsunges = state.samsunges ?: [:]
}
def getVerifiedsamsungPlayer()
{
getsamsungPlayer().findAll{ it?.value?.verified == true }
}
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent << ["hub":hub]
log.trace "${parsedEvent}"
log.trace "${getDeviceType()} - ${parsedEvent.ssdpTerm}"
if (parsedEvent?.ssdpTerm?.contains(getDeviceType()))
{ //SSDP DISCOVERY EVENTS
log.trace "TV found"
def samsunges = getsamsungPlayer()
if (!(samsunges."${parsedEvent.ssdpUSN.toString()}"))
{ //samsung does not exist
log.trace "Adding Device to state..."
samsunges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
}
else
{ // update the values
log.trace "Device was already found in state..."
def d = samsunges."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.trace "Device's port or ip changed..."
}
if (deviceChangedValues) {
def children = getChildDevices()
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.trace "updating dni for device ${it} with mac ${parsedEvent.mac}"
it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists
}
}
}
}
}
else if (parsedEvent.headers && parsedEvent.body)
{ // samsung RESPONSES
def deviceHeaders = parseLanMessage(description, false)
def type = deviceHeaders.headers."content-type"
def body
log.trace "REPONSE TYPE: $type"
if (type?.contains("xml"))
{ // description.xml response (application/xml)
body = new XmlSlurper().parseText(deviceHeaders.body)
log.debug body.device.deviceType.text()
if (body?.device?.deviceType?.text().contains(getDeviceType()))
{
def samsunges = getsamsungPlayer()
def player = samsunges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (player)
{
player.value << [name:body?.device?.friendlyName?.text(),model:body?.device?.modelName?.text(), serialNumber:body?.device?.serialNum?.text(), verified: true]
}
else
{
log.error "The xml file returned a device that didn't exist"
}
}
}
else if(type?.contains("json"))
{ //(application/json)
body = new groovy.json.JsonSlurper().parseText(bodyString)
log.trace "GOT JSON $body"
}
}
else {
log.trace "TV not found..."
//log.trace description
}
}
private def parseEventMessage(String description) {
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString) {
event.ssdpUSN = valueString
}
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString) {
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers')) {
part -= "headers:"
def valueString = part.trim()
if (valueString) {
event.headers = valueString
}
}
else if (part.startsWith('body')) {
part -= "body:"
def valueString = part.trim()
if (valueString) {
event.body = valueString
}
}
}
event
}
def parse(childDevice, description) {
def parsedEvent = parseEventMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = new String(parsedEvent.headers.decodeBase64())
def bodyString = new String(parsedEvent.body.decodeBase64())
log.trace "parse() - ${bodyString}"
def body = new groovy.json.JsonSlurper().parseText(bodyString)
} else {
log.trace "parse - got something other than headers,body..."
return []
}
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress(d) {
def parts = d.split(":")
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}
private Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -76,7 +76,7 @@ def mainPage() {
}
section{
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
//"Custom Message",
"Custom Message",
"Bell 1",
"Bell 2",
"Dogs Barking",
@@ -89,7 +89,7 @@ def mainPage() {
"Someone is arriving",
"Piano",
"Lightsaber"]
//input "message","text",title:"Play this message", required:false, multiple: false
input "message","text",title:"Play this message", required:false, multiple: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
@@ -408,13 +408,15 @@ private loadText() {
case "Lightsaber":
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
break;
default:
/*if (message) {
case "Custom Message":
if (message) {
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
}
else {
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
}*/
}
break;
default:
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
break;
}

View File

@@ -0,0 +1,107 @@
/**
* Device Tile Controller
*
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Device Tile Controller",
namespace: "smartthings/tile-ux",
author: "SmartThings",
description: "A controller SmartApp to install virtual devices into your location in order to simulate various native Device Tiles.",
category: "SmartThings Internal",
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",
singleInstance: true)
preferences {
// landing page
page(name: "defaultPage")
}
def defaultPage() {
dynamicPage(name: "defaultPage", install: true, uninstall: true) {
section {
paragraph "Select on Unselect the devices that you want to install"
}
section(title: "Multi Attribute Tile Types") {
input(type: "bool", name: "genericDeviceTile", title: "generic", description: "A device that showcases the various use of generic multi-attribute-tiles.", defaultValue: "false")
input(type: "bool", name: "lightingDeviceTile", title: "lighting", description: "A device that showcases the various use of lighting multi-attribute-tiles.", defaultValue: "false")
input(type: "bool", name: "thermostatDeviceTile", title: "thermostat", description: "A device that showcases the various use of thermostat multi-attribute-tiles.", defaultValue: "true")
input(type: "bool", name: "mediaPlayerDeviceTile", title: "media player", description: "A device that showcases the various use of mediaPlayer multi-attribute-tiles.", defaultValue: "false")
input(type: "bool", name: "videoPlayerDeviceTile", title: "video player", description: "A device that showcases the various use of videoPlayer multi-attribute-tiles.", defaultValue: "false")
}
section(title: "Device Tile Types") {
input(type: "bool", name: "standardDeviceTile", title: "standard device tiles", description: "A device that showcases the various use of standard device tiles.", defaultValue: "false")
input(type: "bool", name: "valueDeviceTile", title: "value device tiles", description: "A device that showcases the various use of value device tiles.", defaultValue: "false")
input(type: "bool", name: "presenceDeviceTile", title: "presence device tiles", description: "A device that showcases the various use of color control device tile.", defaultValue: "false")
}
section(title: "Other Tile Types") {
input(type: "bool", name: "carouselDeviceTile", title: "image carousel", description: "A device that showcases the various use of carousel device tile.", defaultValue: "false")
input(type: "bool", name: "sliderDeviceTile", title: "slider", description: "A device that showcases the various use of slider device tile.", defaultValue: "false")
input(type: "bool", name: "colorWheelDeviceTile", title: "color wheel", description: "A device that showcases the various use of color wheel device tile.", defaultValue: "false")
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
}
def uninstalled() {
getChildDevices().each {
deleteChildDevice(it.deviceNetworkId)
}
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initializeDevices()
}
def initializeDevices() {
settings.each { key, value ->
log.debug "$key : $value"
def existingDevice = getChildDevices().find { it.name == key }
log.debug "$existingDevice"
if (existingDevice && !value) {
deleteChildDevice(existingDevice.deviceNetworkId)
} else if (!existingDevice && value) {
String dni = UUID.randomUUID()
log.debug "$dni"
addChildDevice(app.namespace, key, dni, null, [
label: labelMap()[key] ?: key,
completedSetup: true
])
}
}
}
// Map the name of the Device to a proper Label
def labelMap() {
[
genericDeviceTile: "Tile Multiattribute Generic",
lightingDeviceTile: "Tile Multiattribute Lighting",
thermostatDeviceTile: "Tile Multiattribute Thermostat",
mediaPlayerDeviceTile: "Tile Multiattribute Media Player",
videoPlayerDeviceTile: "Tile Multiattribute Video Player",
standardDeviceTile: "Tile Device Standard",
valueDeviceTile: "Tile Device Value",
presenceDeviceTile: "Tile Device Presence",
carouselDeviceTile: "Tile Device Carousel",
sliderDeviceTile: "Tile Device Slider",
colorWheelDeviceTile: "Tile Device Color Wheel"
]
}

View File

@@ -16,14 +16,14 @@
* Date: 2013-09-06
*/
definition(
name: "Wemo (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
name: "Wemo (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
)
preferences {
@@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) {
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
HOST: ${deviceNetworkId}
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
}
private verifyDevices() {
@@ -52,6 +52,13 @@ private verifyDevices() {
}
}
void ssdpSubscribe() {
subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler)
}
def firstPage()
{
if(canInstallLabs())
@@ -62,7 +69,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}"
subscribe(location, null, locationHandler, [filterEvents:false])
ssdpSubscribe()
//ssdp request every 25 seconds
if((refreshCount % 5) == 0) {
@@ -105,9 +112,7 @@ def devicesDiscovered() {
def motions = getWemoMotions()
def lightSwitches = getWemoLightSwitches()
def devices = switches + motions + lightSwitches
def list = []
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
}
def switchesDiscovered() {
@@ -175,8 +180,9 @@ def updated() {
def initialize() {
unsubscribe()
unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
unschedule()
ssdpSubscribe()
if (selectedSwitches)
addSwitches()
@@ -189,7 +195,7 @@ def initialize() {
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
runEvery5Minutes("refresh")
}
def resubscribe() {
@@ -199,7 +205,7 @@ def resubscribe() {
def refresh() {
log.debug "refresh() called"
doDeviceSync()
doDeviceSync()
refreshDevices()
}
@@ -228,21 +234,21 @@ def addSwitches() {
def d
if (selectedSwitch) {
d = getChildDevices()?.find {
it.dni == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
}
}
if (!d) {
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
"data": [
"mac": selectedSwitch.value.mac,
"ip": selectedSwitch.value.ip,
"port": selectedSwitch.value.port
]
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
"data": [
"mac": selectedSwitch.value.mac,
"ip": selectedSwitch.value.ip,
"port": selectedSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
@@ -259,7 +265,7 @@ def addMotions() {
def d
if (selectedMotion) {
d = getChildDevices()?.find {
it.dni == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
}
}
@@ -273,9 +279,9 @@ def addMotions() {
"port": selectedMotion.value.port
]
])
def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
}
@@ -290,7 +296,7 @@ def addLightSwitches() {
def d
if (selectedLightSwitch) {
d = getChildDevices()?.find {
it.dni == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
}
}
@@ -304,26 +310,155 @@ def addLightSwitches() {
"port": selectedLightSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
log.debug "found ${d.displayName} with id $dni already exists"
}
}
}
def ssdpSwitchHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = switches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
def ssdpMotionHandler(evt) {
log.info("ssdpMotionHandler")
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { // just update the values
log.debug "Device was already found in state..."
def d = motions."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
}
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
}
}
def ssdpLightSwitchHandler(evt) {
log.info("ssdpLightSwitchHandler")
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def lightSwitches = getWemoLightSwitches()
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
void setupHandler(hubResponse) {
String contentType = hubResponse?.headers['Content-Type']
if (contentType != null && contentType == 'text/xml') {
def body = hubResponse.xml
def wemoDevices = []
String deviceType = body?.device?.deviceType?.text() ?: ""
if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) {
wemoDevices = getWemoSwitches()
} else if (deviceType.startsWith("urn:Belkin:device:sensor")) {
wemoDevices = getWemoMotions()
} else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) {
wemoDevices = getWemoLightSwitches()
}
def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
if (wemoDevice) {
wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true]
} else {
log.error "/setup.xml returned a wemo device that didn't exist"
}
}
}
@Deprecated
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
log.debug parsedEvent
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
//if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
@@ -335,16 +470,20 @@ def locationHandler(evt) {
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
//if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { // just update the values
log.debug "Device was already found in state..."
@@ -391,8 +530,12 @@ def locationHandler(evt) {
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
if (child) {
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
@@ -459,6 +602,7 @@ def locationHandler(evt) {
}
}
@Deprecated
private def parseXmlBody(def body) {
def decodedBytes = body.decodeBase64()
def bodyString
@@ -473,68 +617,48 @@ private def parseXmlBody(def body) {
}
private def parseDiscoveryMessage(String description) {
def device = [:]
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
def valueString = part.split(":")[1].trim()
device.devicetype = valueString
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.mac = valueString
}
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ip = valueString
}
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.port = valueString
}
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ssdpPath = valueString
}
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString) {
device.ssdpUSN = valueString
}
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString) {
device.ssdpTerm = valueString
}
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
def valueString = part.trim()
if (valueString) {
device.headers = valueString
}
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
def valueString = part.trim()
if (valueString) {
device.body = valueString
}
event.body = part.trim()
}
}
device
event
}
def doDeviceSync(){
@@ -560,4 +684,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}

View File

@@ -1,11 +1,17 @@
/**
* Vacation Lighting Director
*
* Version 2.4 - Added information paragraphs
* Version 2.5 - Moved scheduling over to Cron and added time as a trigger.
* Cleaned up formatting and some typos.
* Updated license.
* Made people option optional
* Added sttement to unschedule on mode change if people option is not selected
*
* Version 2.4 - Added information paragraphs
*
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
*
* Copyright 2015 Tim Slagle
* Copyright 2016 Tim Slagle
*
* 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:
@@ -34,6 +40,7 @@ preferences {
page name:"pageSetup"
page name:"Setup"
page name:"Settings"
page name: "timeIntervalInput"
}
@@ -51,8 +58,7 @@ def pageSetup() {
return dynamicPage(pageProperties) {
section(""){
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
"Please use each othe the sections below to setup the different preferences to your liking. " +
"I recommend this app be used with at least two away modes. An example would be 'Away Day' 'and Away Night'. "
"Please use each of the the sections below to setup the different preferences to your liking. "
}
section("Setup Menu") {
href "Setup", title: "Setup", description: "", state:greyedOut()
@@ -70,7 +76,7 @@ def Setup() {
def newMode = [
name: "newMode",
type: "mode",
title: "Which?",
title: "Modes",
multiple: true,
required: true
]
@@ -96,14 +102,6 @@ def Setup() {
required: true,
]
def people = [
name: "people",
type: "capability.presenceSensor",
title: "If these people are home do not change light status",
required: true,
multiple: true
]
def pageName = "Setup"
def pageProperties = [
@@ -116,10 +114,11 @@ def Setup() {
section(""){
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
paragraph "you are away. All of these settings are required in order for the simulator to run correctly."
"you are away. All of these settings are required in order for the simulator to run correctly."
}
section("Which mode change triggers the simulator? (This app will only run in selected mode(s))") {
input newMode
section("Simulator Triggers") {
input newMode
href "timeIntervalInput", title: "Times", description: timeIntervalLabel(), refreshAfterSelection:true
}
section("Light switches to turn on/off") {
input switches
@@ -130,9 +129,6 @@ def Setup() {
section("Number of active lights at any given time") {
input number_of_active_lights
}
section("People") {
input people
}
}
}
@@ -162,30 +158,58 @@ def Settings() {
title: "Settings",
nextPage: "pageSetup"
]
def people = [
name: "people",
type: "capability.presenceSensor",
title: "If these people are home do not change light status",
required: false,
multiple: true
]
return dynamicPage(pageProperties) {
section(""){
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
paragraph "as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
"as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
}
section("Delay to start simulator") {
input falseAlarmThreshold
}
section("People") {
paragraph "Not using this setting may cause some lights to remain on when you arrive home"
input people
}
section("More options") {
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input days
}
}
}
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
def timeIntervalInput() {
dynamicPage(name: "timeIntervalInput") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
if (startTimeType in ["sunrise","sunset"]) {
input "startTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
}
else {
input "starting", "time", title: "Start time", required: false
}
}
section {
input "endTimeType", "enum", title: "Ending at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
if (endTimeType in ["sunrise","sunset"]) {
input "endTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
}
else {
input "ending", "time", title: "End time", required: false
}
}
}
}
def installed() {
initialize()
}
@@ -201,10 +225,13 @@ def initialize(){
if (newMode != null) {
subscribe(location, modeChangeHandler)
}
if (starting != null) {
schedule(starting, modeChangeHandler)
}
log.debug "Installed with settings: ${settings}"
}
def modeChangeHandler(evt) {
log.debug "Mode change to: ${evt.value}"
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
runIn(delay, scheduleCheck)
}
@@ -212,48 +239,54 @@ def modeChangeHandler(evt) {
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
def scheduleCheck(evt) {
if(allOk){
log.debug("Running")
// turn off all the switches
switches.off()
// grab a random switch
def random = new Random()
def inactive_switches = switches
for (int i = 0 ; i < number_of_active_lights ; i++) {
// if there are no inactive switches to turn on then let's break
if (inactive_switches.size() == 0){
break
if(allOk){
log.debug("Running")
// turn off all the switches
switches.off()
// grab a random switch
def random = new Random()
def inactive_switches = switches
for (int i = 0 ; i < number_of_active_lights ; i++) {
// if there are no inactive switches to turn on then let's break
if (inactive_switches.size() == 0){
break
}
// grab a random switch and turn it on
def random_int = random.nextInt(inactive_switches.size())
inactive_switches[random_int].on()
// then remove that switch from the pool off switches that can be turned on
inactive_switches.remove(random_int)
}
// re-run again when the frequency demands it
schedule("0 0/${frequency_minutes} * 1/1 * ? *", scheduleCheck)
}
// grab a random switch and turn it on
def random_int = random.nextInt(inactive_switches.size())
inactive_switches[random_int].on()
// then remove that switch from the pool off switches that can be turned on
inactive_switches.remove(random_int)
}
// re-run again when the frequency demands it
runIn(frequency_minutes * 60, scheduleCheck)
}
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
else if (modeOk) {
log.debug("mode OK. Running again")
runIn(frequency_minutes * 60, scheduleCheck)
switches.off()
}
//if none is ok turn off frequency check and turn off lights.
else if(people){
//don't turn off lights if anyone is home
if(someoneIsHome()){
log.debug("Stopping Check for Light")
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
else if (modeOk) {
log.debug("mode OK. Running again")
switches.off()
}
//if none is ok turn off frequency check and turn off lights.
else {
if(people){
//don't turn off lights if anyone is home
if(someoneIsHome()){
log.debug("Stopping Check for Light")
unschedule()
}
else{
log.debug("Stopping Check for Light and turning off all lights")
switches.off()
unschedule()
}
}
else{
log.debug("Stopping Check for Light and turning off all lights")
switches.off()
else if (!modeOk) {
unschedule()
}
}
}
}
@@ -286,26 +319,6 @@ private getDaysOk() {
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
else if (starting){
result = currTime >= start
}
else if (ending){
result = currTime <= stop
}
log.trace "timeOk = $result"
result
}
private getHomeIsEmpty() {
def result = true
@@ -330,25 +343,59 @@ private getSomeoneIsHome() {
return result
}
//gets the label for time restriction. Label phrasing changes depending on if there is both start and stop times or just one start/stop time.
def getTimeLabel(starting, ending){
def timeLabel = "Tap to set"
if(starting && ending){
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
}
else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
}
else if(ending){
timeLabel = "End at" + hhmm(ending)
}
timeLabel
private getTimeOk() {
def result = true
def start = timeWindowStart()
def stop = timeWindowStop()
if (start && stop && location.timeZone) {
result = timeOfDayIsBetween(start, stop, new Date(), location.timeZone)
}
log.trace "timeOk = $result"
result
}
private timeWindowStart() {
def result = null
if (startTimeType == "sunrise") {
result = location.currentState("sunriseTime")?.dateValue
if (result && startTimeOffset) {
result = new Date(result.time + Math.round(startTimeOffset * 60000))
}
}
else if (startTimeType == "sunset") {
result = location.currentState("sunsetTime")?.dateValue
if (result && startTimeOffset) {
result = new Date(result.time + Math.round(startTimeOffset * 60000))
}
}
else if (starting && location.timeZone) {
result = timeToday(starting, location.timeZone)
}
log.trace "timeWindowStart = ${result}"
result
}
private timeWindowStop() {
def result = null
if (endTimeType == "sunrise") {
result = location.currentState("sunriseTime")?.dateValue
if (result && endTimeOffset) {
result = new Date(result.time + Math.round(endTimeOffset * 60000))
}
}
else if (endTimeType == "sunset") {
result = location.currentState("sunsetTime")?.dateValue
if (result && endTimeOffset) {
result = new Date(result.time + Math.round(endTimeOffset * 60000))
}
}
else if (ending && location.timeZone) {
result = timeToday(ending, location.timeZone)
}
log.trace "timeWindowStop = ${result}"
result
}
//fomrats time to readable format for time label
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
@@ -357,6 +404,41 @@ private hhmm(time, fmt = "h:mm a")
f.format(t)
}
private timeIntervalLabel() {
def start = ""
switch (startTimeType) {
case "time":
if (ending) {
start += hhmm(starting)
}
break
case "sunrise":
case "sunset":
start += startTimeType[0].toUpperCase() + startTimeType[1..-1]
if (startTimeOffset) {
start += startTimeOffset > 0 ? "+${startTimeOffset} min" : "${startTimeOffset} min"
}
break
}
def finish = ""
switch (endTimeType) {
case "time":
if (ending) {
finish += hhmm(ending)
}
break
case "sunrise":
case "sunset":
finish += endTimeType[0].toUpperCase() + endTimeType[1..-1]
if (endTimeOffset) {
finish += endTimeOffset > 0 ? "+${endTimeOffset} min" : "${endTimeOffset} min"
}
break
}
start && finish ? "${start} to ${finish}" : ""
}
//sets complete/not complete for the setup section on the main dynamic page
def greyedOut(){
def result = ""
@@ -369,16 +451,7 @@ def greyedOut(){
//sets complete/not complete for the settings section on the main dynamic page
def greyedOutSettings(){
def result = ""
if (starting || ending || days || falseAlarmThreshold) {
result = "complete"
}
result
}
//sets complete/not complete for time restriction section in settings
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
if (people || days || falseAlarmThreshold ) {
result = "complete"
}
result