Compare commits

..

82 Commits

Author SHA1 Message Date
Dillon Miller
2b040d9ae8 MSA-1184: A device handler for Aeotec Model zw095-a Home Energy Monitor. I have been using it for several weeks and have enhanced and refined it to the point where it is ready for general use by others. This device handler has been posted on the support site of the manufacturer.
https://aeotec.freshdesk.com/support/solutions/articles/6000122372-using-hem-gen5-with-smartthings-hub-s-custom-device-type

Thanks,
-Dillon
2016-04-15 11:15:57 -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
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
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
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
Nowak
8a5f0af0e2 added missing capability 2016-03-17 16:02:14 +01: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
Nowak
1af43681a5 updated tiles 2016-02-29 15:10:26 +01:00
Nowak
b211b298c0 updated tiles 2016-02-29 15:10:02 +01: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
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
45 changed files with 4424 additions and 459 deletions

View File

@@ -1,15 +1,18 @@
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-hipchat'
apply plugin: 'smartthings-slack'
buildscript {
dependencies {
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.3"
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.7"
}
repositories {
mavenLocal()
jcenter()
maven {
credentials {
@@ -29,7 +32,43 @@ repositories {
dependencies {
}
hipchatShareFile {
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()) {
@@ -42,19 +81,25 @@ hipchatShareFile {
}
}
}
Date date = new Date()
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
// Set task properties
data = archives.join('\n').getBytes(StandardCharsets.UTF_8)
fileName = 'deployment-notes.txt'
contentType = 'text/html'
}
hipchatSendNotification {
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
message = "Began executable deploy of SmartThingsPublic(${branch})."
if (branch == 'master') {
message += ' (dev shards)'
}
color = branch == 'master' ? 'yellow' : 'red'
notify = true
// 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
)
}

View File

@@ -15,13 +15,11 @@ deployment:
develop:
branch: master
commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_DEV
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
- ./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 -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_STAGING
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
- ./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,442 @@
/*
* Aeon HEM Gen5(zwave plus)
*
* Copyright 2016 Dillon A. Miller
*
* v0.8 of Aeon HEM Gen5(zwave plus) code, released 04/15/2016 for Aeotec Model zw095-a
* This Gen5 device handler is not backward compatible with the Aeon V1 or V2 device. If your model number is not zw095-a, don't use it.
*
* 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.
*
* Some code used from various SmartThings device handlers created by:
* Brock Haymond, Barry A. Burke, and Robert Vandervoort. Portions of the metering code came from ElasticDev.
*
* Link to find the latest version of the Device Handler code:
* https://gist.github.com/DuncanIdahoCT/deb2bafdd28af4fce3073b9d9f4ecafa
*
* General notes:
* You may need to change the device type to this device handler after you pair it to your hub. I'm not sure why it doesn't always automatically pair using this handler even though the fingerprint matches...
* Also, you need to hit the button on the back of the HEM Gen5 a couple (two-three) times to get it to flash the red light on the front rapidly to ensure it's in inclusion mode. It isn't in the right mode if it's just flashing slow.
* Once the red light is solid, look in your things list and find it, could be just called z-wave, then change it's name and device type using the graph api to this custom handler to make it start working.
* The config with default intervals will send right away after you set the HEM Gen5 to this handler, please wait a few minutes before sending the config again or changing the intervals under preferences.
* The purpose of the config button is just really to resend the config with monitor interval prefs, as sometimes the config isn't fully applied, See the list of config items at the bottom of this code.
* You may need to send the config a few times, with about 2 minutes delay between to get all the properties to take. Not sure why-but if you look in the debug log in the graph api for this device you can see if they all applied.
* I had to unplug and replug my device after extensive testing and repeatedly setting the config over and over. Could be it works for you the first time, or you may need to pull power and re plug too in order to get all the Pole 1/2 data to show in the app.
*
* Known Issues:
* Issue 1:
* Not sure if this is the device handler or just a general SmartThings IOS App issue but sometimes when you set the preferences, it just spins in the app.
* If you are watching the log you can see it did set the preferences. Not sure why the app gets stuck, but just reload the app and should be fine.
* Issue 2:
* Not really a problem, more like a limitation; the clamps 1/2 data packets are sent to the ST hub based on monitor intervals for reports groups 1-3 which you can set in the device preferences.
* What this means is that, if you are paying attention to the tile data, you'll notice the center Total values don't quite work out to be the sum of Pole 1/2 values.
* This is simply because the base HEM data can be "polled" whereas the clamp 1/2 data is sent. And only sent on a schedule.
*
* Change log:
* v0.1 - released 04/04/16:
* Added support for secure inclusion and command encapsulation
* v0.2 - released 04/05/16:
* Added configuration settings using some preference variables that you can control from the app
* v0.3 - released 04/05/16:
* Added clamp1 and clamp2 data display, may have to hit configure a few times (wait at least 2 minute each time) to make the top left and right boxes show data from the clamps.
* v0.4 - released 04/06/16:
* Changed the "main" tile to display a clean total kWh, although the ST app seems to make everything on the main Things list all CAPS so it's actually displayed as KWH...
* v0.5 - released 04/07/16:
* Changed the main thing list device icon to st.Lighting.light14 cause it has a leaf!.
* v0.6 - released 04/07/16:
* Added a cost per kWh preference and a cost tile that is calculated on kWh.
* Also added a timestamp of last reset button tap to work with above cost feature.
* v0.7 - released 04/08/16:
* Removed individual value tile polling actions and polling function.
* Cleaned up and well-formed code
* v0.8 - released 04/15/16:
* Slightly adjusted the size of the tiles to fit all tiles into the App screen without scrolling.
* Further commented and cleaned up the code in preparation for submission to SmartThings as an official device handler.
* Submitted for consideration by SmartThings.
*
* To do:
* Features:
* Color code tiles or tile values for hi/low usage conditions.
* Possibly integrate a second tile set page with peak/min usage of watts and/or amps values over time.
* Integrate PlotWatt or other online Energy tracking API based service.
* Fixes:
* Determine why app sometimes freezes when setting preferences, I have seen this with other devices, e.g. Arrival Sensor, Jasco Wall Switches, etc...
*
*/
// metadata
metadata {
definition (name: "Aeon HEM Gen5(zwave plus)", namespace: "DuncanIdahoCT", author: "Dillon A. Miller") {
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
command "reset"
fingerprint deviceId: "0x3101", inClusters: "0x98"
fingerprint inClusters: "0x5E,0x86,0x72,0x32,0x56,0x60,0x70,0x59,0x85,0x7A,0x73,0xEF,0x5A", outClusters: "0x82"
}
// simulator
simulator {
for (int i = 0; i <= 10000; i += 1000) {
status "power ${i} W":
new physicalgraph.zwave.Zwave().meterV3.meterReport(scaledMeterValue: i, precision: 3, meterType: 1, scale: 2, size: 4).incomingMessage()
}
for (int i = 0; i <= 100; i += 10) {
status "energy ${i} kWh":
new physicalgraph.zwave.Zwave().meterV3.meterReport(scaledMeterValue: i, precision: 3, meterType: 1, scale: 0, size: 4).incomingMessage()
}
}
// tile definitions
tiles (scale: 2) {
// This tile is not displayed on the tile screen for this device but rather in the Things list.
valueTile("list-energy", "device.energy") {
state "default", label:'${currentValue} kWh', icon: "st.Lighting.light14"
}
// Tiles with a digit below relate to Clamp 1 and Clamp 2. Tiles with no digit are totals for clamps or volts.
valueTile("current1", "device.current1", decoration: "flat", width: 2, height: 2) {
state "default", label:'Pole 1\n${currentValue}\nAmps'
}
valueTile("current", "device.current", decoration: "flat", width: 2, height: 2) {
state "default", label:'Total\n${currentValue}\nAmps'
}
valueTile("current2", "device.current2", decoration: "flat", width: 2, height: 2) {
state "default", label:'Pole 2\n${currentValue}\nAmps'
}
valueTile("power1", "device.power1", decoration: "flat", width: 2, height: 2) {
state "default", label:'Pole 1\n${currentValue}\nWatts'
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
state "default", label:'Total\n${currentValue}\nWatts'
}
valueTile("power2", "device.power2", decoration: "flat", width: 2, height: 2) {
state "default", label:'Pole 2\n${currentValue}\nWatts'
}
valueTile("voltage", "device.voltage", decoration: "flat", width: 2, height: 1) {
state "default", label:'${currentValue} V'
}
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 1) {
state "default", label:'${currentValue} kWh'
}
valueTile("cost", "device.cost", decoration: "flat", width: 2, height: 1) {
state "default", label:'\$${currentValue}'
}
valueTile("lastresetlabel", "device.lastresettime", decoration: "flat", width: 3, height: 1) {
state "default", label:'Last Reset Timestamp'
}
valueTile("lastresettime", "device.lastresettime", decoration: "flat", width: 3, height: 1) {
state "default", label:'${currentValue}'
}
standardTile("configure", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'reset kWh', action:"reset"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "list-energy"
details(["current1","current","current2","power1","power","power2","voltage","energy","cost","lastresetlabel","lastresettime","configure","reset","refresh"])
}
preferences {
input "kWhCost", "string",
title: "Cost in \$/kWh",
description: "Your Electric Bill Cost Per kWh",
defaultValue: "0.19514" as String,
required: false,
displayDuringSetup: true
input "monitorInterval1", "integer",
title: "Volts & kWh Report",
description: "Interval (secs) for Volts & kWh Report",
defaultValue: 60,
range: "1..4294967295?",
required: false,
displayDuringSetup: true
input "monitorInterval2", "integer",
title: "Amps Report",
description: "Interval (secs) for Amps Report",
defaultValue: 30,
range: "1..4294967295?",
required: false,
displayDuringSetup: true
input "monitorInterval3", "integer",
title: "Watts Report",
description: "Interval (secs) for Watts Report",
defaultValue: 6,
range: "1..4294967295?",
required: false,
displayDuringSetup: true
}
}
def updated(){
if (state.sec && !isConfigured()) {
// in case we miss the SCSR
response(configure())
}
}
def parse(String description){
def result = null
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description != "updated") {
def cmd = zwave.parse(description, [0x32: 3, 0x56: 1, 0x59: 1, 0x5A: 1, 0x60: 3, 0x70: 1, 0x72: 2, 0x73: 1, 0x82: 1, 0x85: 2, 0x86: 2, 0x8E: 2, 0xEF: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
//log.debug "Parsed '${description}' to ${result.inspect()}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x32: 3, 0x56: 1, 0x59: 1, 0x5A: 1, 0x60: 3, 0x70: 1, 0x72: 2, 0x73: 1, 0x82: 1, 0x85: 2, 0x86: 2, 0x8E: 2, 0xEF: 1])
state.sec = 1
//log.debug "encapsulated: ${encapsulatedCommand}"
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
response(configure())
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
log.debug "---ASSOCIATION REPORT V2--- ${device.displayName} groupingIdentifier: ${cmd.groupingIdentifier}, maxNodesSupported: ${cmd.maxNodesSupported}, nodeId: ${cmd.nodeId}, reportsToFollow: ${cmd.reportsToFollow}"
}
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
def meterTypes = ["Unknown", "Electric", "Gas", "Water"]
def electricNames = ["energy", "energy", "power", "count", "voltage", "current", "powerFactor", "unknown"]
def electricUnits = ["kWh", "kVAh", "W", "pulses", "V", "A", "Power Factor", ""]
//NOTE ScaledPreviousMeterValue does not always contain a value
def previousValue = cmd.scaledPreviousMeterValue ?: 0
//Here is where all HEM polled values are defined. Scale(0-7) is in reference to the Aeon Labs HEM Gen5 data for kWh, kVAh, W, V, A, and M.S.T. respectively.
//If scale 7 (M.S.T.) is polled, you would receive Scale2(0-1) which is kVar, and kVarh respectively. We are ignoring the Scale2 ranges in this device handler.
def map = [ name: electricNames[cmd.scale], unit: electricUnits[cmd.scale], displayed: state.display]
switch(cmd.scale) {
case 0: //kWh
previousValue = device.currentValue("energy") ?: cmd.scaledPreviousMeterValue ?: 0
BigDecimal costDecimal = cmd.scaledMeterValue * (kWhCost as BigDecimal)
def costDisplay = String.format("%5.2f",costDecimal)
sendEvent(name: "cost", value: costDisplay, unit: "", descriptionText: "Display Cost: \$${costDisp}")
map.value = cmd.scaledMeterValue
break;
case 1: //kVAh (not used in the U.S.)
map.value = cmd.scaledMeterValue
break;
case 2: //Watts
previousValue = device.currentValue("power") ?: cmd.scaledPreviousMeterValue ?: 0
map.value = Math.round(cmd.scaledMeterValue)
break;
case 3: //pulses
map.value = Math.round(cmd.scaledMeterValue)
break;
case 4: //Volts
previousValue = device.currentValue("voltage") ?: cmd.scaledPreviousMeterValue ?: 0
map.value = cmd.scaledMeterValue
break;
case 5: //Amps
previousValue = device.currentValue("current") ?: cmd.scaledPreviousMeterValue ?: 0
map.value = cmd.scaledMeterValue
break;
case 6: //Power Factor
case 7: //Scale2 values (not currently implimented or needed)
map.value = cmd.scaledMeterValue
break;
default:
break;
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
//This is where the HEM clamp1 and clamp2 (subdevice) report values are defined. Scale(2,5) is in reference to the Aeon Labs HEM Gen5 (subdevice) data for W, and A respectively.
//Z-Wave Command Class 0x60 (multichannelv3) is necessary to interpret the subdevice data from the HEM clamps.
//In addition, "cmd.commandClass == 50" and "encapsulatedCommand([0x30: 1, 0x31: 1])" below is necessary to properly receive and inturpret the encasulated subdevice data sent to the SmartThings hub by the HEM.
//The numbered "command class" references: 50, 0x30v1, and 0x31v1 do not seem to be true Z-Wave Command Classes and any correlation is seemingly coincidental.
//It should also be noted that without the above, the data received will not be processed here under the 0x60 (multichannelv3) command class and you will see unhandled messages from the HEM along with references to command class 50 as well as Meter Types 33, and 161.
//sourceEndPoint 1, and 2 are the Clamps 1, and 2.
def dispValue
def newValue
def formattedValue
def MAX_AMPS = 220
def MAX_WATTS = 24000
if (cmd.commandClass == 50) { //50 is likely a manufacturer specific code, Z-Wave specifies this as a "Basic Window Covering" so it's not a true Z-Wave Command Class.
def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 1, 0x31: 1]) // The documentation on working with Z-Wave subdevices and the technical specs from Aeon Labs do not explain this adequately, but it's necessary.
//log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
if (encapsulatedCommand) {
if (cmd.sourceEndPoint == 1) {
if (encapsulatedCommand.scale == 2 ) {
newValue = Math.round(encapsulatedCommand.scaledMeterValue)
if (newValue > MAX_WATTS) { return }
formattedValue = newValue
dispValue = "${formattedValue}"
sendEvent(name: "power1", value: dispValue as String, unit: "", descriptionText: "L1 Power: ${formattedValue} Watts")
}
if (encapsulatedCommand.scale == 5 ) {
newValue = Math.round(encapsulatedCommand.scaledMeterValue * 100) / 100
if (newValue > MAX_AMPS) { return }
formattedValue = String.format("%5.2f", newValue)
dispValue = "${formattedValue}"
sendEvent(name: "current1", value: dispValue as String, unit: "", descriptionText: "L1 Current: ${formattedValue} Amps")
}
}
else if (cmd.sourceEndPoint == 2) {
if (encapsulatedCommand.scale == 2 ) {
newValue = Math.round(encapsulatedCommand.scaledMeterValue)
if (newValue > MAX_WATTS) { return }
formattedValue = newValue
dispValue = "${formattedValue}"
sendEvent(name: "power2", value: dispValue as String, unit: "", descriptionText: "L2 Power: ${formattedValue} Watts")
}
if (encapsulatedCommand.scale == 5 ) {
newValue = Math.round(encapsulatedCommand.scaledMeterValue * 100) / 100
if (newValue > MAX_AMPS) { return }
formattedValue = String.format("%5.2f", newValue)
dispValue = "${formattedValue}"
sendEvent(name: "current2", value: dispValue as String, unit: "", descriptionText: "L2 Current: ${formattedValue} Amps")
}
}
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
//This will log any unhandled command output to the debug window.
log.debug "Unhandled: $cmd"
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
def refresh() {
def request = [
//This is where the tile action "refresh" is defined. Refresh is very basic. It simply gets and displays the latest values from the HEM exclusive of the clamp subdevices.
zwave.meterV3.meterGet(scale: 0), //kWh
zwave.meterV3.meterGet(scale: 2), //Wattage
zwave.meterV3.meterGet(scale: 4), //Volts
zwave.meterV3.meterGet(scale: 5), //Amps
]
commands(request)
}
def reset() {
//This is where the tile action "reset" is defined. Reset is only meant to be used once a month on the end/beginning of your electric utility billing cycle.
//Tapping reset will send the meter reset command to HEM and zero out the kWh data so you can start fresh.
//This will also clear the cost data and reset the last reset timestamp. Finally it will poll for latest values from the HEM.
//This has no impact on Pole1 or Pole2 (clamp1 and clamp2 subdevice) tile data as that is sent via reports from the HEM.
def dateString = new Date().format("M/d/YY", location.timeZone)
def timeString = new Date().format("h:mm a", location.timeZone)
state.lastresettime = dateString+" @ "+timeString
sendEvent(name: "lastresettime", value: state.lastresettime)
def request = [
zwave.meterV3.meterReset(),
zwave.meterV3.meterGet(scale: 0), //kWh
zwave.meterV3.meterGet(scale: 2), //Wattage
zwave.meterV3.meterGet(scale: 4), //Volts
zwave.meterV3.meterGet(scale: 5), //Amps
]
commands(request)
}
def configure() {
//This is where the tile action "configure" is defined. Configure resends the configuration commands below (using the variables set by the preferences section above) to the HEM Gen5 device.
//If you're watching the debug log when you tap configure, you should see the full configuration report come back slowly over about a minute.
//If you don't see the full configuration report (seven messages) followed by the association report, tap configure again.
def monitorInt1 = 60
if (monitorInterval1) {
monitorInt1=monitorInterval1.toInteger()
}
def monitorInt2 = 30
if (monitorInterval2) {
monitorInt2=monitorInterval2.toInteger()
}
def monitorInt3 = 6
if (monitorInterval3) {
monitorInt3=monitorInterval3.toInteger()
}
log.debug "Sending configure commands - kWhCost '${kWhCost}', monitorInterval1 '${monitorInt1}', monitorInterval2 '${monitorInt2}', monitorInterval3 '${monitorInt3}'"
def request = [
// Reset switch configuration to defaults.
//zwave.configurationV1.configurationSet(parameterNumber: 255, size: 1, scaledConfigurationValue: 1),
// Disable selective reporting, so always update based on schedule below <set to 1 to reduce network traffic>.
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 1),
// (DISABLED by first option) Don't send unless watts have changed by 50 <default>.
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 10),
// (DISABLED by first option) Or by 10% <default>.
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: 5),
// Which reports need to send in Report group 1.
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 6149),
// Which reports need to send in Report group 2.
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1572872),
// Which reports need to send in Report group 3.
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 770),
// Interval to send Report group 1.
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: monitorInt1),
// Interval to send Report group 2.
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: monitorInt2),
// Interval to send Report group 3.
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: monitorInt3),
// Report which configuration commands were sent to and received by the HEM Gen5 successfully.
zwave.configurationV1.configurationGet(parameterNumber: 3),
zwave.configurationV1.configurationGet(parameterNumber: 4),
zwave.configurationV1.configurationGet(parameterNumber: 8),
zwave.configurationV1.configurationGet(parameterNumber: 101),
zwave.configurationV1.configurationGet(parameterNumber: 102),
zwave.configurationV1.configurationGet(parameterNumber: 103),
zwave.configurationV1.configurationGet(parameterNumber: 111),
zwave.configurationV1.configurationGet(parameterNumber: 112),
zwave.configurationV1.configurationGet(parameterNumber: 113),
zwave.associationV2.associationGet(groupingIdentifier: 1)
]
commands(request)
}
private setConfigured() {
updateDataValue("configured", "true")
}
private isConfigured() {
getDataValue("configured") == "true"
}
private command(physicalgraph.zwave.Command cmd) {
if (state.sec) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
cmd.format()
}
}
private commands(commands, delay=500) {
delayBetween(commands.collect{ command(it) }, delay)
}

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

@@ -1,16 +1,16 @@
#==============================================================================
# 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
# 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
# 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
@@ -23,13 +23,14 @@
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Give your device a name'''.ko=기기 이름 설정
'''Set Device Image'''.ko=기기 이미지 설정
'''Presence timeout (minutes)'''.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 }}집을 나갔습니다.
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
#==============================================================================

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,8 +1,11 @@
/**
* Hue Bulb
*
* Philips Hue Type "Extended Color Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
@@ -43,16 +46,10 @@ metadata {
}
}
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("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'
}
@@ -60,29 +57,12 @@ metadata {
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", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
state "hue", label: 'Hue ${currentValue} '
}
main(["switch"])
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
@@ -91,11 +71,13 @@ metadata {
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}")
}
@@ -125,62 +107,104 @@ void nextLevel() {
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
parent.setLevel(this, percent)
sendEvent(name: "level", value: 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'"
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: 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'"
parent.setHue(this, percent)
sendEvent(name: "hue", value: 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"
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)}
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, hex:"#90C638", saturation:56, hue:23]
log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
parent.poll()
}
void setAdjustedColor(value) {
if (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) {
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()
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
@@ -199,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
@@ -23,10 +25,10 @@ metadata {
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)"
@@ -36,13 +38,6 @@ metadata {
}
}
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"
}
@@ -51,7 +46,7 @@ metadata {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
main(["rich-control"])
details(["rich-control", "refresh"])
}
}
@@ -75,19 +70,24 @@ def parse(description) {
// handle commands
void on() {
parent.on(this)
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
parent.off(this)
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
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"
}
}
void refresh() {

View File

@@ -23,10 +23,10 @@
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Give your device a name'''.ko=기기 이름 설정
'''Set Device Image'''.ko=기기 이미지 설정
# Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
'''present'''.ko=집안
'''not present'''.ko=외출

View File

@@ -22,10 +22,14 @@
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Outlet'''.ko=플러그
'''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 }}와트입니다
'''{{ 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

@@ -65,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'

View File

@@ -22,6 +22,8 @@
#==============================================================================
# Korean (ko)
# Device Preferences
'''Dry'''.ko=건조
'''Wet'''.ko=누수
'''dry'''.ko=건조
'''wet'''.ko=누수
'''battery'''.ko=배터리
@@ -29,13 +31,14 @@
'''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=누수센서
'''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 }}°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 }}%입니다.
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
#==============================================================================

View File

@@ -28,13 +28,16 @@
'''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=기기 이름 바꾸기
'''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 }} 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 }}%입니다.
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
#==============================================================================

View File

@@ -31,15 +31,20 @@
'''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=멀티 센서
'''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 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 }}%입니다.
'''{{ 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

@@ -83,19 +83,19 @@ metadata {
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}°',

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

@@ -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

@@ -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

@@ -83,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
}
@@ -121,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"
@@ -156,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

@@ -51,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

@@ -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

@@ -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

@@ -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
}

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

View File

@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
//singleInstance: true
singleInstance: true
)
preferences {
@@ -60,7 +60,7 @@ def bridgeDiscovery(params=[:])
app.updateSetting("selectedHue", "")
}
subscribe(location, null, locationHandler, [filterEvents:false])
ssdpSubscribe()
//bridge discovery request every 15 //25 seconds
if((bridgeRefreshCount % 5) == 0) {
@@ -68,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()
}
@@ -152,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()
@@ -161,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() {
@@ -171,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() {
@@ -288,17 +293,52 @@ def bulbListHandler(hub, 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]
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 ->
@@ -308,11 +348,7 @@ 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 {
@@ -321,16 +357,15 @@ def addBulbs() {
} 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()
}
} 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)
}
}
}
@@ -352,8 +387,9 @@ 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)
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
newbridge = false
}
}
@@ -382,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"
@@ -417,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..."
@@ -460,8 +603,9 @@ def locationHandler(evt) {
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...
@@ -472,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]
}
}
}
@@ -486,11 +630,7 @@ 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()
}
@@ -602,6 +742,20 @@ def parse(childDevice, description) {
}
}
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])
@@ -642,35 +796,53 @@ def setColorTemperature(childDevice, huesettings) {
}
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
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
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.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
if (huesettings.switch) {
value.on = huesettings.switch == "on"
}
// 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)
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) {
@@ -685,15 +857,11 @@ 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) {
@@ -743,6 +911,57 @@ private getBridgeIP() {
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)
}
@@ -752,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
}

View File

@@ -23,9 +23,9 @@
'''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={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ 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

@@ -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

@@ -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