From 91c01dc643afb2c35894ec67af810ef3dccdd7fc Mon Sep 17 00:00:00 2001 From: Luke Bredeson Date: Thu, 29 Oct 2015 12:35:28 -0500 Subject: [PATCH 01/14] DST fix for Button Controller --- .../button-controller.src/button-controller.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/smartthings/button-controller.src/button-controller.groovy b/smartapps/smartthings/button-controller.src/button-controller.groovy index 1f99b08..d03551b 100644 --- a/smartapps/smartthings/button-controller.src/button-controller.groovy +++ b/smartapps/smartthings/button-controller.src/button-controller.groovy @@ -294,8 +294,8 @@ private getTimeOk() { def result = true if (starting && ending) { def currTime = now() - def start = timeToday(starting).time - def stop = timeToday(ending).time + def start = timeToday(starting, location.timeZone).time + def stop = timeToday(ending, location.timeZone).time result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start } log.trace "timeOk = $result" From 27c05f4e5b6f79a8a86611f5526e627d6c497683 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Fri, 6 May 2016 09:29:41 -0700 Subject: [PATCH 02/14] DVCSMP-1738 Philips HUE: Detail page showing the Brightness level twice -Removed secondary control from all Hue DTH --- devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy | 3 --- devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy | 3 --- devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy | 3 --- 3 files changed, 9 deletions(-) diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index 952b183..0c10c51 100644 --- a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -37,9 +37,6 @@ metadata { 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" } diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 950ac51..d533006 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -38,9 +38,6 @@ metadata { 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" } diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index a1d7f33..2944ce3 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -33,9 +33,6 @@ metadata { 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}%' - } } controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { From bd62962ee1db268727934aa379c57613e6c5545a Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Wed, 11 May 2016 23:06:09 -0700 Subject: [PATCH 03/14] DVCSMP-1716 Remove heartbeat from device types -Remove heartbeat from OSRAM white bulb and Smartpower Outlet -Heartbeat was a hack previously used for Amazon Echo --- .../osram-lightify-led-tunable-white-60w.groovy | 9 --------- .../smartpower-outlet.src/smartpower-outlet.groovy | 7 ------- 2 files changed, 16 deletions(-) diff --git a/devicetypes/smartthings/osram-lightify-led-tunable-white-60w.src/osram-lightify-led-tunable-white-60w.groovy b/devicetypes/smartthings/osram-lightify-led-tunable-white-60w.src/osram-lightify-led-tunable-white-60w.groovy index 8253582..80fa3ba 100644 --- a/devicetypes/smartthings/osram-lightify-led-tunable-white-60w.src/osram-lightify-led-tunable-white-60w.groovy +++ b/devicetypes/smartthings/osram-lightify-led-tunable-white-60w.src/osram-lightify-led-tunable-white-60w.groovy @@ -19,11 +19,6 @@ metadata { capability "Sensor" attribute "colorName", "string" - - // indicates that device keeps track of heartbeat (in state.heartbeat) - attribute "heartbeat", "string" - - } // simulator metadata @@ -75,9 +70,6 @@ metadata { def parse(String description) { //log.trace description - // save heartbeat (i.e. last time we got a message from device) - state.heartbeat = Calendar.getInstance().getTimeInMillis() - if (description?.startsWith("catchall:")) { if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1")) { @@ -132,7 +124,6 @@ def off() { } def refresh() { - sendEvent(name: "heartbeat", value: "alive", displayed:false) [ "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500", "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500", diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 1b47c3e..d28d8cf 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -25,9 +25,6 @@ metadata { capability "Sensor" capability "Health Check" - // indicates that device keeps track of heartbeat (in state.heartbeat) - attribute "heartbeat", "string" - fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet" @@ -81,9 +78,6 @@ metadata { def parse(String description) { log.debug "description is $description" - // save heartbeat (i.e. last time we got a message from device) - state.heartbeat = Calendar.getInstance().getTimeInMillis() - def finalResult = zigbee.getKnownDescription(description) //TODO: Remove this after getKnownDescription can parse it automatically @@ -124,7 +118,6 @@ def on() { } def refresh() { - sendEvent(name: "heartbeat", value: "alive", displayed:false) zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B") } From a133406b6e3d495befdb868270e843c50325f0de Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Wed, 11 May 2016 23:41:19 -0700 Subject: [PATCH 04/14] DVCSMP-1770 Add MSR to zwave-switch and configure at pairing -Make ZWave switch and Dimmer switch behave the same and save MSR and manufacturer name in data --- .../dimmer-switch.src/dimmer-switch.groovy | 1 + .../smartthings/zwave-switch.src/zwave-switch.groovy | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/devicetypes/smartthings/dimmer-switch.src/dimmer-switch.groovy b/devicetypes/smartthings/dimmer-switch.src/dimmer-switch.groovy index 6b7eca1..11af042 100644 --- a/devicetypes/smartthings/dimmer-switch.src/dimmer-switch.groovy +++ b/devicetypes/smartthings/dimmer-switch.src/dimmer-switch.groovy @@ -138,6 +138,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS log.debug "productTypeId: ${cmd.productTypeId}" def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) updateDataValue("MSR", msr) + updateDataValue("manufacturer", cmd.manufacturerName) createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false]) } diff --git a/devicetypes/smartthings/zwave-switch.src/zwave-switch.groovy b/devicetypes/smartthings/zwave-switch.src/zwave-switch.groovy index cda2e69..798aa63 100644 --- a/devicetypes/smartthings/zwave-switch.src/zwave-switch.groovy +++ b/devicetypes/smartthings/zwave-switch.src/zwave-switch.groovy @@ -95,11 +95,17 @@ def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) { } def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { - if (state.manufacturer != cmd.manufacturerName) { - updateDataValue("manufacturer", cmd.manufacturerName) - } + log.debug "manufacturerId: ${cmd.manufacturerId}" + log.debug "manufacturerName: ${cmd.manufacturerName}" + log.debug "productId: ${cmd.productId}" + log.debug "productTypeId: ${cmd.productTypeId}" + def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) + updateDataValue("MSR", msr) + updateDataValue("manufacturer", cmd.manufacturerName) + createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false]) } + def zwaveEvent(physicalgraph.zwave.Command cmd) { // Handles all Z-Wave commands we aren't interested in [:] From 45b78eff8d24c04e2e6f107e37a2680c83b1c190 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Mon, 9 May 2016 11:25:18 -0700 Subject: [PATCH 05/14] DVCSMP-1699 Na02>Missing/Failed Oauth Tokens - Netatmo - refactored the oauth component for the app to work in different shards, if supported - fixed some white spacing issues and indents - addressed comments - edited spacing --- .../netatmo-connect.groovy | 450 ++++++++---------- 1 file changed, 208 insertions(+), 242 deletions(-) diff --git a/smartapps/dianoga/netatmo-connect.src/netatmo-connect.groovy b/smartapps/dianoga/netatmo-connect.src/netatmo-connect.groovy index 905cdde..a127d5b 100644 --- a/smartapps/dianoga/netatmo-connect.src/netatmo-connect.groovy +++ b/smartapps/dianoga/netatmo-connect.src/netatmo-connect.groovy @@ -4,29 +4,33 @@ import java.text.DecimalFormat import groovy.json.JsonSlurper -private apiUrl() { "https://api.netatmo.com" } -private getVendorName() { "netatmo" } -private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" } -private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" } +private getApiUrl() { "https://api.netatmo.com" } +private getVendorName() { "netatmo" } +private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" } +private getVendorTokenPath(){ "${apiUrl}/oauth2/token" } private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" } -private getClientId() { appSettings.clientId } -private getClientSecret() { appSettings.clientSecret } -private getServerUrl() { "https://graph.api.smartthings.com" } +private getClientId() { appSettings.clientId } +private getClientSecret() { appSettings.clientSecret } +private getServerUrl() { appSettings.serverUrl } +private getShardUrl() { return getApiServerUrl() } +private getCallbackUrl() { "${serverUrl}/oauth/callback" } +private getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${shardUrl}" } // Automatically generated. Make future change here. definition( - name: "Netatmo (Connect)", - namespace: "dianoga", - author: "Brian Steere", - description: "Netatmo Integration", - category: "SmartThings Labs", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png", - oauth: true, - singleInstance: true + name: "Netatmo (Connect)", + namespace: "dianoga", + author: "Brian Steere", + description: "Netatmo Integration", + category: "SmartThings Labs", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png", + oauth: true, + singleInstance: true ){ appSetting "clientId" appSetting "clientSecret" + appSetting "serverUrl" } preferences { @@ -35,35 +39,52 @@ preferences { } mappings { - path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]} - path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]} - path("/auth"){action: [GET: "auth"]} + path("/oauth/initialize") {action: [GET: "oauthInitUrl"]} + path("/oauth/callback") {action: [GET: "callback"]} } def authPage() { log.debug "In authPage" - if(canInstallLabs()) { - def description = null - if (state.vendorAccessToken == null) { - log.debug "About to create access token." + def description + def uninstallAllowed = false + def oauthTokenProvided = false - createAccessToken() - description = "Tap to enter Credentials." + if (!state.accessToken) { + log.debug "About to create access token." + state.accessToken = createAccessToken() + } - return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) { - section { href url:buildRedirectUrl("auth"), style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description } + if (canInstallLabs()) { + + def redirectUrl = getBuildRedirectUrl() + log.debug "Redirect url = ${redirectUrl}" + + if (state.authToken) { + description = "Tap 'Next' to proceed" + uninstallAllowed = true + oauthTokenProvided = true + } else { + description = "Click to enter Credentials." + } + + if (!oauthTokenProvided) { + log.debug "Show the login page" + return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) { + section() { + paragraph "Tap below to log in to the netatmo and authorize SmartThings access." + href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description + } } } else { - description = "Tap 'Next' to proceed" - - return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) { - section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description } + log.debug "Show the devices page" + return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) { + section() { + input(name:"Devices", style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description) + } } } - } - else - { + } else { def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date. To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub".""" @@ -78,229 +99,175 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next } } -def auth() { - redirect location: oauthInitUrl() -} - def oauthInitUrl() { log.debug "In oauthInitUrl" - /* OAuth Step 1: Request access code with our client ID */ - state.oauthInitState = UUID.randomUUID().toString() - def oauthParams = [ response_type: "code", - client_id: getClientId(), - state: state.oauthInitState, - redirect_uri: buildRedirectUrl("receiveToken") , - scope: "read_station" - ] - - return getVendorAuthPath() + toQueryString(oauthParams) -} - -def buildRedirectUrl(endPoint) { - log.debug "In buildRedirectUrl" - - return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}" -} - -def receiveToken() { - log.debug "In receiveToken" - def oauthParams = [ - client_secret: getClientSecret(), + response_type: "code", client_id: getClientId(), - grant_type: "authorization_code", - redirect_uri: buildRedirectUrl('receiveToken'), - code: params.code, + client_secret: getClientSecret(), + state: state.oauthInitState, + redirect_uri: getCallbackUrl(), scope: "read_station" - ] - - def tokenUrl = getVendorTokenPath() - def params = [ - uri: tokenUrl, - contentType: 'application/x-www-form-urlencoded', - body: oauthParams, ] - log.debug params + log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}" - /* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */ - try { - httpPost(params) { response -> - log.debug response.data - def slurper = new JsonSlurper(); + redirect (location: getVendorAuthPath() + toQueryString(oauthParams)) +} - response.data.each {key, value -> - def data = slurper.parseText(key); - log.debug "Data: $data" +def callback() { + log.debug "callback()>> params: $params, params.code ${params.code}" - state.vendorRefreshToken = data.refresh_token - state.vendorAccessToken = data.access_token - state.vendorTokenExpires = now() + (data.expires_in * 1000) - return + def code = params.code + def oauthState = params.state + + if (oauthState == state.oauthInitState) { + + def tokenParams = [ + client_secret: getClientSecret(), + client_id : getClientId(), + grant_type: "authorization_code", + redirect_uri: getCallbackUrl(), + code: code, + scope: "read_station" + ] + + log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}" + + def tokenUrl = getVendorTokenPath() + def params = [ + uri: tokenUrl, + contentType: 'application/x-www-form-urlencoded', + body: tokenParams + ] + + log.debug "PARAMS: ${params}" + + httpPost(params) { resp -> + + def slurper = new JsonSlurper() + + resp.data.each { key, value -> + def data = slurper.parseText(key) + + state.refreshToken = data.refresh_token + state.authToken = data.access_token + state.tokenExpires = now() + (data.expires_in * 1000) + log.debug "swapped token: $resp.data" } - } - } catch (Exception e) { - log.debug "Error: $e" + + // Handle success and failure here, and render stuff accordingly + if (state.authToken) { + success() + } else { + fail() + } + + } else { + log.error "callback() failed oauthState != state.oauthInitState" } +} - log.debug "State: $state" +def success() { + log.debug "in success" + def message = """ +

We have located your """ + getVendorName() + """ account.

+

Tap 'Done' to continue to Devices.

+ """ + connectionStatus(message) +} - if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install - return +def fail() { + log.debug "in fail" + def message = """ +

The connection could not be established!

+

Click 'Done' to return to the menu.

+ """ + connectionStatus(message) +} + +def connectionStatus(message, redirectUrl = null) { + def redirectHtml = "" + if (redirectUrl) { + redirectHtml = """ + + """ } - /* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */ - def html = """ - - - - - ${getVendorName()} Connection - - - -
- Vendor icon - connected device icon - SmartThings logo -

We have located your """ + getVendorName() + """ account.

-

Tap 'Done' to process your credentials.

+ + + + + ${getVendorName()} Connection + + + +
+ Vendor icon + connected device icon + SmartThings logo + ${message}
- """ + """ render contentType: 'text/html', data: html } -def receivedToken() { - log.debug "In receivedToken" - - def html = """ - - - - - Withings Connection - - - -
- Vendor icon - connected device icon - SmartThings logo -

Tap 'Done' to continue to Devices.

-
- - - """ - render contentType: 'text/html', data: html -} - -// " - def refreshToken() { log.debug "In refreshToken" @@ -308,8 +275,8 @@ def refreshToken() { client_secret: getClientSecret(), client_id: getClientId(), grant_type: "refresh_token", - refresh_token: state.vendorRefreshToken - ] + refresh_token: state.refreshToken + ] def tokenUrl = getVendorTokenPath() def params = [ @@ -318,7 +285,7 @@ def refreshToken() { body: oauthParams, ] - /* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */ + // OAuth Step 2: Request access token with our client Secret and OAuth "Code" try { httpPost(params) { response -> def slurper = new JsonSlurper(); @@ -327,9 +294,9 @@ def refreshToken() { def data = slurper.parseText(key); log.debug "Data: $data" - state.vendorRefreshToken = data.refresh_token - state.vendorAccessToken = data.access_token - state.vendorTokenExpires = now() + (data.expires_in * 1000) + state.refreshToken = data.refresh_token + state.accessToken = data.access_token + state.tokenExpires = now() + (data.expires_in * 1000) return true } @@ -338,9 +305,8 @@ def refreshToken() { log.debug "Error: $e" } - log.debug "State: $state" - - if ( !state.vendorAccessToken ) { //We didn't get an access token + // We didn't get an access token + if ( !state.accessToken ) { return false } } @@ -482,13 +448,13 @@ def listDevices() { } def apiGet(String path, Map query, Closure callback) { - if(now() >= state.vendorTokenExpires) { + if(now() >= state.tokenExpires) { refreshToken(); } - query['access_token'] = state.vendorAccessToken + query['access_token'] = state.accessToken def params = [ - uri: apiUrl(), + uri: getApiUrl(), path: path, 'query': query ] From b33d6216961bcce067b7404ab2629b97095195c5 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Tue, 10 May 2016 11:35:01 -0700 Subject: [PATCH 06/14] DVCSMP-1699 Na02>Missing/Failed Oauth Tokens - now calling the method to get the right shard url for Life360 - removed serverUrl from the app settings --- .../smartthings/life360-connect.src/life360-connect.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/smartapps/smartthings/life360-connect.src/life360-connect.groovy b/smartapps/smartthings/life360-connect.src/life360-connect.groovy index b5d6555..cf07750 100644 --- a/smartapps/smartthings/life360-connect.src/life360-connect.groovy +++ b/smartapps/smartthings/life360-connect.src/life360-connect.groovy @@ -27,7 +27,6 @@ definition( ) { appSetting "clientId" appSetting "clientSecret" - appSetting "serverUrl" } preferences { @@ -192,7 +191,7 @@ def getSmartThingsClientId() { return "pREqugabRetre4EstetherufrePumamExucrEHuc" } -def getServerUrl() { appSettings.serverUrl } +def getServerUrl() { getApiServerUrl() } def buildRedirectUrl() { From 56eef9cf22b50e607056cf8284160579df1ee42f Mon Sep 17 00:00:00 2001 From: Matthew Hartley Date: Thu, 12 May 2016 14:28:40 -0500 Subject: [PATCH 07/14] MSA-1278: The Prempoint SmartApp is a service bridge to allow Prempoint users to import, share, and manage devices that are controlled by their SmartThings hub. --- .../prempoint.src/prempoint.groovy | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 smartapps/prempoint-com/prempoint.src/prempoint.groovy diff --git a/smartapps/prempoint-com/prempoint.src/prempoint.groovy b/smartapps/prempoint-com/prempoint.src/prempoint.groovy new file mode 100644 index 0000000..5bf9584 --- /dev/null +++ b/smartapps/prempoint-com/prempoint.src/prempoint.groovy @@ -0,0 +1,345 @@ +/** + * SmartThings service for Prempoint + * + * Author: Prempoint Inc. (c) 2016 + * + */ +definition( + name: "Prempoint", + namespace: "prempoint.com", + author: "Prempoint Inc.", + description: "SmartThings service for Prempoint", + category: "Connections", + iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png", + iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png", + iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png", + oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"]) + +preferences { + section("Allow Prempoint to Control & Access These Things...") { + input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false + input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false + input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false + //input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false + input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false + } +} + +mappings { + path("/list") { + action: [ + GET: "listDevices" + ] + } + path("/switches") { + action: [ + GET: "listSwitches" + ] + } + path("/switches/:id") { + action: [ + GET: "showSwitch" + ] + } + path("/switches/:id/:command") { + action: [ + GET: "updateSwitch" + ] + } + path("/switches/:id/:command/:level") { + action: [ + GET: "updateSwitch" + ] + } + path("/locks") { + action: [ + GET: "listLocks" + ] + } + path("/locks/:id") { + action: [ + GET: "showLock" + ] + } + path("/locks/:id/:command") { + action: [ + GET: "updateLock" + ] + } + path("/doors/:id") { + action: [ + GET: "showDoor" + ] + } + path("/doors/:id/:command") { + action: [ + GET: "updateDoor" + ] + } + path("/garagedoors/:id") { + action: [ + GET: "showGarageDoor" + ] + } + path("/garagedoors/:id/:command") { + action: [ + GET: "updateGarageDoor" + ] + } + path("/cameras/:id") { + action: [ + GET: "showCamera" + ] + } + path("/cameras/:id/:command") { + action: [ + GET: "updateCamera" + ] + } +} + +def installed() {} + +def updated() {} + +def listDevices() { + log.debug "entering listDevices" + //return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras() + return listSwitches() + listLocks() + listGarageDoors() + listCameras() +} + +//switches +def listSwitches() { + log.debug "entering listSwitches" + switches.collect{showDevice(it,"switch")} +} + +def showSwitch() { + log.debug "entering showSwitches" + show(switches, "switch") +} + +def updateSwitch() { + log.debug "entering updateSwitches" + update(switches, "switch") +} + +//locks +def listLocks() { + log.debug "entering listLocks" + locks.collect{showDevice(it,"lock")} +} + +def showLock() { + log.debug "entering showLock" + show(locks, "lock") +} + +def updateLock() { + log.debug "entering updateLock" + update(locks, "lock") +} + +//doors +def listDoors() { + log.debug "entering listDoors" + locks.collect{showDevice(it,"door")} +} + +def showDoor() { + log.debug "entering showDoors" + show(doors, "door") +} + +def updateDoor() { + log.debug "entering updateDoor" + update(doors, "door") +} + +//garagedoors +def listGarageDoors() { + log.debug "entering listGarageDoors" + locks.collect{showDevice(it,"garagedoor")} +} + +def showGarageDoor() { + log.debug "entering showGarageDoors" + show(garagedoors, "garagedoor") +} + +def updateGarageDoor() { + log.debug "entering updateGarageDoor" + update(gargedoors, "garagedoor") +} + +//cameras +def listCameras() { + log.debug "entering listCameras" + cameras.collect{showDevice(it,"image")} +} + +def showCamera() { + log.debug "entering showCameras" + show(cameras, "camera") +} + +def updateCamera() { + log.debug "entering updateCamera" + update(cameras, "camera") +} + +def deviceHandler(evt) {} + +private update(devices, type) { + def rc = null + + //def command = request.JSON?.command + def command = params.command + + log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command" + + // Process the command. + if (command) + { + def dev = devices.find { it.id == params.id } + if (!dev) { + httpError(404, "Device not found: $params.id") + } else if (type == "switch") { + switch(command) { + case "on": + rc = dev.on() + break + case "off": + rc = dev.off() + break + default: + httpError(400, "Device command=$command is not a valid for device=$it.id $dev") + } + } else if (type == "lock") { + switch(command) { + case "lock": + rc = dev.lock() + break + case "unlock": + rc = dev.unlock() + break + default: + httpError(400, "Device command=$command is not a valid for device:=$it.id $dev") + } + } else if (type == "door") { + switch(command) { + case "open": + rc = dev.open() + break + case "close": + rc = dev.close() + break + default: + httpError(400, "Device command=$command is not a valid for device=$it.id $dev") + } + } else if (type == "garagedoor") { + switch(command) { + case "open": + rc = dev.open() + break + case "close": + rc = dev.close() + break + default: + httpError(400, "Device command=$command is not a valid for device=$it.id $dev") + } + } else if (type == "camera") { + switch(command) { + case "take": + rc = dev.take() + log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage" + break + default: + httpError(400, "Device command=$command is not a valid for device=$it.id $dev") + } + } + + log.debug "executed device=$it.id $dev command=$command rc=$rc" + + // Check that the device is a switch that is currently on, supports 'setLevel" + // and that a level was specified. + int level = params.level ? params.level as int : -1; + if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) { + log.debug "device about to setLevel=$level" + dev.setLevel(level); + } + + // Show the device info if necessary. + if (rc == null) { + rc = showDevice(dev, type) + } + } + + return rc +} + +private show(devices, type) { + def dev = devices.find { it.id == params.id } + if (!dev) { + httpError(404, "Device not found") + } else { + // Show the device info. + showDevice(dev, type) + } +} + +private showDevice(it, type) { + def props = null + + // Get the current state for the device type. + def state = [it.currentState(type)] + + // Check that whether the a switch device with level support is located and update the returned device type. + def devType = type + + if (type == "switch" && hasLevel(it)) { + // Assign "switchWithLevel" to device type. + devType = "switchWithLevel" + // Add the level state. + def levelState = it.currentState("level") + if (levelState) { + state.add(levelState) + } + } + + log.debug "device label=$it.label type=$devType" + + // Assign the device item properties if appropriate. + if (it) { + props = [id: it.id, label: it.label, type: devType, state: state] + // Add the hub information to the device properties + // if appropriate. + if (it.hub) { + props.put("location", it.hub.hub.location) + } + if (it.currentImage) { + props.put("currentImage", it.currentImage) + } + } + + return props +} + +private hasLevel(device) { + // Default return value. + def rc = false; + + // Get the device supported commands. + def supportedCommands = device.supportedCommands + + // Check to see if the "setLevel" was found and assign + // the appropriate return value. + if (supportedCommands) { + // Find the "setLevel" command. + rc = supportedCommands.toString().indexOf("setLevel") != -1 + } + + log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc" + + return rc +} \ No newline at end of file From f77c5810c6f5182a2dbd0cbfa2d8b709b82738a5 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 13 May 2016 10:26:45 -0500 Subject: [PATCH 08/14] DOCS-284 - update tiles for best practices, add setColor to colorWheel tile --- .../tile-basic-colorwheel.groovy | 9 +++++- .../tile-basic-slider.groovy | 2 +- .../tile-basic-standard.groovy | 14 ++++----- .../tile-basic-value.groovy | 28 ++++++++--------- .../tile-multiattribute-generic.groovy | 22 +++++++------- .../tile-multiattribute-lighting.groovy | 7 ++--- .../tile-multiattribute-mediaplayer.groovy | 6 ++-- .../tile-multiattribute-thermostat.groovy | 30 ++++++++++--------- 8 files changed, 62 insertions(+), 56 deletions(-) diff --git a/devicetypes/smartthings/tile-ux/tile-basic-colorwheel.src/tile-basic-colorwheel.groovy b/devicetypes/smartthings/tile-ux/tile-basic-colorwheel.src/tile-basic-colorwheel.groovy index 8086c2c..102f9e3 100644 --- a/devicetypes/smartthings/tile-ux/tile-basic-colorwheel.src/tile-basic-colorwheel.groovy +++ b/devicetypes/smartthings/tile-ux/tile-basic-colorwheel.src/tile-basic-colorwheel.groovy @@ -22,7 +22,7 @@ metadata { tiles(scale: 2) { valueTile("currentColor", "device.color") { - state "default", label: '${currentValue}' + state "color", label: '${currentValue}', defaultState: true } controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) { @@ -41,6 +41,13 @@ def parse(String description) { log.debug "Parsing '${description}'" } +def setColor(value) { + log.debug "setting color: $value" + if (value.hex) { sendEvent(name: "color", value: value.hex) } + if (value.hue) { sendEvent(name: "hue", value: value.hue) } + if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) } +} + def setSaturation(percent) { log.debug "Executing 'setSaturation'" sendEvent(name: "saturation", value: percent) diff --git a/devicetypes/smartthings/tile-ux/tile-basic-slider.src/tile-basic-slider.groovy b/devicetypes/smartthings/tile-ux/tile-basic-slider.src/tile-basic-slider.groovy index 12e30be..923b4d4 100644 --- a/devicetypes/smartthings/tile-ux/tile-basic-slider.src/tile-basic-slider.groovy +++ b/devicetypes/smartthings/tile-ux/tile-basic-slider.src/tile-basic-slider.groovy @@ -39,7 +39,7 @@ metadata { } valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) { - state "default", label:'${currentValue}' + state "range", label:'${currentValue}', defaultState: true } controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") { diff --git a/devicetypes/smartthings/tile-ux/tile-basic-standard.src/tile-basic-standard.groovy b/devicetypes/smartthings/tile-ux/tile-basic-standard.src/tile-basic-standard.groovy index d2b7e35..1f4f029 100644 --- a/devicetypes/smartthings/tile-ux/tile-basic-standard.src/tile-basic-standard.groovy +++ b/devicetypes/smartthings/tile-ux/tile-basic-standard.src/tile-basic-standard.groovy @@ -41,17 +41,17 @@ metadata { // 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" + state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true } // 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" + state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true } // 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" + state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true } // standard with defaultState = true @@ -74,19 +74,19 @@ metadata { // utility tiles to fill the spaces standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") { - state "default", label:'' + state "emptySmall", label:'', defaultState: true } standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") { - state "default", label:'' + state "emptyBigger", label:'', defaultState: true } // multi-line text (explicit newlines) standardTile("multiLine", "device.multiLine", width: 2, height: 2) { - state "default", label: '${currentValue}' + state "multiLine", label: '${currentValue}', defaultState: true } standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) { - state "default", label: '${currentValue}', icon: "st.switches.switch.off" + state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true } main("actionRings") diff --git a/devicetypes/smartthings/tile-ux/tile-basic-value.src/tile-basic-value.groovy b/devicetypes/smartthings/tile-ux/tile-basic-value.src/tile-basic-value.groovy index 8ea7dee..e1efb8a 100644 --- a/devicetypes/smartthings/tile-ux/tile-basic-value.src/tile-basic-value.groovy +++ b/devicetypes/smartthings/tile-ux/tile-basic-value.src/tile-basic-value.groovy @@ -22,68 +22,68 @@ metadata { tiles(scale: 2) { valueTile("text", "device.text", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("longText", "device.longText", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("integer", "device.integer", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("pi", "device.pi", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) { - state "default", label:'${currentValue}' + state "val", label:'${currentValue}', defaultState: true } valueTile("bgColor", "device.integer", width: 2, height: 2) { - state "default", label:'${currentValue}', backgroundColor: "#e86d13" + state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true } valueTile("bgColorRange", "device.integer", width: 2, height: 2) { - state "default", label:'${currentValue}', backgroundColors: [ + state "val", label:'${currentValue}', defaultState: true, backgroundColors: [ [value: 10, color: "#ff0000"], [value: 90, color: "#0000ff"] ] } valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) { - state "default", label:'${currentValue}', backgroundColors: [ + state "val", label:'${currentValue}', defaultState: true, backgroundColors: [ [value: 10, color: "#333333"] ] } valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) { - state "default", label:'${currentValue}', backgroundColors: [ + state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [ [value: 10, color: "#990000"], [value: 10, color: "#000099"] ] } valueTile("noValue", "device.nada", width: 4, height: 2) { - state "default", label:'${currentValue}' + state "noval", label:'${currentValue}', defaultState: true } valueTile("multiLine", "device.multiLine", width: 3, height: 2) { - state "default", label: '${currentValue}' + state "val", label: '${currentValue}', defaultState: true } valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) { - state "default", label: '${currentValue}', icon: "st.switches.switch.off" + state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true } main("text") details([ - "text", "longText", "integer", + "text", "longText", "integer", "integerFloat", "pi", "floatAsText", "bgColor", "bgColorRange", "bgColorRangeSingleItem", "bgColorRangeConflict", "noValue", diff --git a/devicetypes/smartthings/tile-ux/tile-multiattribute-generic.src/tile-multiattribute-generic.groovy b/devicetypes/smartthings/tile-ux/tile-multiattribute-generic.src/tile-multiattribute-generic.groovy index 61f09d9..d535647 100644 --- a/devicetypes/smartthings/tile-ux/tile-multiattribute-generic.src/tile-multiattribute-generic.groovy +++ b/devicetypes/smartthings/tile-ux/tile-multiattribute-generic.src/tile-multiattribute-generic.groovy @@ -39,15 +39,15 @@ metadata { attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn" } tileAttribute("device.level", key: "SECONDARY_CONTROL") { - attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel" + attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true } tileAttribute("device.level", key: "SLIDER_CONTROL") { - attributeState "default", action:"switch level.setLevel" + attributeState "level", action:"switch level.setLevel", defaultState: true } } multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) { tileAttribute("device.level", key: "PRIMARY_CONTROL") { - attributeState "default", label:'${currentValue}', backgroundColors:[ + attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[ [value: 0, color: "#ff0000"], [value: 20, color: "#ffff00"], [value: 40, color: "#00ff00"], @@ -69,34 +69,34 @@ metadata { } multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) { tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") { - attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821" + attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true } tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") { - attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821" + attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true } } multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) { tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") { - attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821" + attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true } tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") { - attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821" + attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true } } multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) { tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") { - attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on" + attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true } tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") { - attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on" + attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true } } multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) { tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") { - attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on" + attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true } tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") { - attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on" + attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true } } diff --git a/devicetypes/smartthings/tile-ux/tile-multiattribute-lighting.src/tile-multiattribute-lighting.groovy b/devicetypes/smartthings/tile-ux/tile-multiattribute-lighting.src/tile-multiattribute-lighting.groovy index 6ebbb3e..7ffaabc 100644 --- a/devicetypes/smartthings/tile-ux/tile-multiattribute-lighting.src/tile-multiattribute-lighting.groovy +++ b/devicetypes/smartthings/tile-ux/tile-multiattribute-lighting.src/tile-multiattribute-lighting.groovy @@ -96,10 +96,10 @@ metadata { } 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" + state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true } standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true } main(["switch"]) @@ -173,7 +173,6 @@ def setColor(value) { def reset() { log.debug "Executing 'reset'" setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23]) - //parent.poll() } def setAdjustedColor(value) { @@ -189,7 +188,6 @@ def setAdjustedColor(value) { def refresh() { log.debug "Executing 'refresh'" - //parent.manualRefresh() } def adjustOutgoingHue(percent) { @@ -208,4 +206,3 @@ def adjustOutgoingHue(percent) { log.info "percent: $percent, adjusted: $adjusted" adjusted } - diff --git a/devicetypes/smartthings/tile-ux/tile-multiattribute-mediaplayer.src/tile-multiattribute-mediaplayer.groovy b/devicetypes/smartthings/tile-ux/tile-multiattribute-mediaplayer.src/tile-multiattribute-mediaplayer.groovy index b22c3f7..2ad41f4 100644 --- a/devicetypes/smartthings/tile-ux/tile-multiattribute-mediaplayer.src/tile-multiattribute-mediaplayer.groovy +++ b/devicetypes/smartthings/tile-ux/tile-multiattribute-mediaplayer.src/tile-multiattribute-mediaplayer.groovy @@ -37,10 +37,10 @@ metadata { attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing") } tileAttribute("device.status", key: "PREVIOUS_TRACK") { - attributeState("default", action:"music Player.previousTrack") + attributeState("status", action:"music Player.previousTrack", defaultState: true) } tileAttribute("device.status", key: "NEXT_TRACK") { - attributeState("default", action:"music Player.nextTrack") + attributeState("status", action:"music Player.nextTrack", defaultState: true) } tileAttribute ("device.level", key: "SLIDER_CONTROL") { attributeState("level", action:"music Player.setLevel") @@ -50,7 +50,7 @@ metadata { attributeState("muted", action:"music Player.unmute", nextState: "unmuted") } tileAttribute("device.trackDescription", key: "MARQUEE") { - attributeState("default", label:"${currentValue}") + attributeState("trackDescription", label:"${currentValue}", defaultState: true) } } diff --git a/devicetypes/smartthings/tile-ux/tile-multiattribute-thermostat.src/tile-multiattribute-thermostat.groovy b/devicetypes/smartthings/tile-ux/tile-multiattribute-thermostat.src/tile-multiattribute-thermostat.groovy index 6f8d979..6fb57bd 100644 --- a/devicetypes/smartthings/tile-ux/tile-multiattribute-thermostat.src/tile-multiattribute-thermostat.groovy +++ b/devicetypes/smartthings/tile-ux/tile-multiattribute-thermostat.src/tile-multiattribute-thermostat.groovy @@ -32,14 +32,14 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true) } 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:"%") + attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true) } tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { attributeState("idle", backgroundColor:"#44b621") @@ -53,15 +53,16 @@ metadata { attributeState("auto", label:'${name}') } tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) } tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) } } multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) + attributeState("temp", label:'${currentValue}', unit:"dF") } tileAttribute("device.temperature", key: "VALUE_CONTROL") { attributeState("VALUE_UP", action: "tempUp") @@ -79,15 +80,16 @@ metadata { attributeState("auto", label:'${name}') } tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) + attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF") } tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { - attributeState("default", label:'${currentValue}', unit:"dF") + attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) } } multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { - attributeState("default", label:'${currentValue}', unit:"dF", + attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true, backgroundColors:[ [value: 31, color: "#153591"], [value: 44, color: "#1e9cbb"], @@ -118,30 +120,30 @@ metadata { ) } standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:'down', action:"tempDown" + state "tempDown", label:'down', action:"tempDown", defaultState: true } standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:'up', action:"tempUp" + state "tempUp", label:'up', action:"tempUp", defaultState: true } 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" + state "heatDown", label:'down', action:"heatDown", defaultState: true } standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:'up', action:"heatUp" + state "heatUp", label:'up', action:"heatUp", defaultState: true } 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" + state "coolDown", label:'down', action:"coolDown", defaultState: true } standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:'up', action:"coolUp" + state "coolUp", label:'up', action:"coolUp", defaultState: true } standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { From 8de3276ce6f91f1d00b8e129e036c996a4532066 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Fri, 13 May 2016 11:13:13 -0700 Subject: [PATCH 09/14] DVCSMP-1709 Android>SmartApps>PlatLinkConnector>Unable To Add - now getting the right shard url --- .../plantlink-connector.src/plantlink-connector.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/smartapps/osotech/plantlink-connector.src/plantlink-connector.groovy b/smartapps/osotech/plantlink-connector.src/plantlink-connector.groovy index 12cad96..6a1423d 100644 --- a/smartapps/osotech/plantlink-connector.src/plantlink-connector.groovy +++ b/smartapps/osotech/plantlink-connector.src/plantlink-connector.groovy @@ -370,9 +370,7 @@ def parse_api_response(resp, message) { } } -def getServerUrl() { - return "https://graph.api.smartthings.com" -} +def getServerUrl() { return getApiServerUrl() } def debugEvent(message, displayEvent) { def results = [ From 26a0f6f9398c86f18d242647e2e3bc5a7157a8cf Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Fri, 13 May 2016 21:10:54 -0500 Subject: [PATCH 10/14] DOCS-284 adding README for example/test tiles --- devicetypes/smartthings/tile-ux/README.md | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 devicetypes/smartthings/tile-ux/README.md diff --git a/devicetypes/smartthings/tile-ux/README.md b/devicetypes/smartthings/tile-ux/README.md new file mode 100644 index 0000000..ac1b48b --- /dev/null +++ b/devicetypes/smartthings/tile-ux/README.md @@ -0,0 +1,42 @@ +# Device Tiles Examples and Reference + +This package contains examples of Device tiles, organized by tile type. + +## Purpose + +Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured. + +The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation. + +## Installation + +1. Self-publish the Device Handlers in this package. +2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy). +3. Install the SmartApp from the Marketplace, under "My Apps". +4. Select the simulated devices you want to install and press "Done". + +The simulated devices can then be found in the "Things" view of "My Home" in the mobile app. +You may wish to create a new room for these simulated devices for easy access. + +## Usage + +Each simulated device can be interacted with like other devices. +You can use the mobile app to interact with the tiles to see how they look and behave. + +## Troubleshooting + +If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself. +Also check live logging to see if there is a specific tile that is causing installation issues. + +## FAQ + +*Question: A tile isn't behaving as expected. What should I do?* + +QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices. + +Developers may file a support ticket, and reference the specific tile and issue observed. + +*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?* + +We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage. +That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue. From 293a73136e671863db4dce4a37b6649a61f47718 Mon Sep 17 00:00:00 2001 From: jackchi Date: Mon, 16 May 2016 11:43:21 -0700 Subject: [PATCH 11/14] [CHF-74] Add category to SmartSense DTH --- .../smartthings/smartpower-outlet.src/smartpower-outlet.groovy | 2 +- .../smartsense-moisture-sensor.groovy | 2 +- .../smartsense-motion-sensor.groovy | 2 +- .../smartsense-multi-sensor.src/smartsense-multi-sensor.groovy | 2 +- .../smartsense-open-closed-accelerometer-sensor.groovy | 2 +- .../smartsense-temp-humidity-sensor.groovy | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index d28d8cf..11febf1 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -16,7 +16,7 @@ metadata { // Automatically generated. Make future change here. - definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") { capability "Actuator" capability "Switch" capability "Power Meter" diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index de0526b..8248bf0 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -15,7 +15,7 @@ */ metadata { - definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { capability "Configuration" capability "Battery" capability "Refresh" diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index 29d9dd0..660b0b7 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -15,7 +15,7 @@ */ metadata { - definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { capability "Motion Sensor" capability "Configuration" capability "Battery" diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 892e5ad..1e30759 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -15,7 +15,7 @@ */ metadata { - definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { capability "Three Axis" capability "Battery" diff --git a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy index eb2bfdd..9f03f17 100644 --- a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy @@ -16,7 +16,7 @@ //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH metadata { - definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { capability "Battery" capability "Configuration" capability "Contact Sensor" diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index a938cb9..0479b5d 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -14,7 +14,7 @@ * */ metadata { - definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") { + definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { capability "Configuration" capability "Battery" capability "Refresh" From 85a335d365a91c514a0dc4dfcdc266f666828aa4 Mon Sep 17 00:00:00 2001 From: chelseaokey Date: Wed, 18 May 2016 16:46:17 -0500 Subject: [PATCH 12/14] INTL-527 Temperature Notifications Should Respect Location Temp Scale --- smartapps/smartthings/its-too-cold.src/its-too-cold.groovy | 3 ++- smartapps/smartthings/its-too-hot.src/its-too-hot.groovy | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/smartapps/smartthings/its-too-cold.src/its-too-cold.groovy b/smartapps/smartthings/its-too-cold.src/its-too-cold.groovy index 0e66cc3..8364c20 100644 --- a/smartapps/smartthings/its-too-cold.src/its-too-cold.groovy +++ b/smartapps/smartthings/its-too-cold.src/its-too-cold.groovy @@ -73,7 +73,8 @@ def temperatureHandler(evt) { // TODO: Send "Temperature back to normal" SMS, turn switch off } else { log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch" - send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:"F"}") + def tempScale = location.temperatureScale ?: "F" + send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}") switch1?.on() } } diff --git a/smartapps/smartthings/its-too-hot.src/its-too-hot.groovy b/smartapps/smartthings/its-too-hot.src/its-too-hot.groovy index e9c8c0f..5b8e10d 100644 --- a/smartapps/smartthings/its-too-hot.src/its-too-hot.groovy +++ b/smartapps/smartthings/its-too-hot.src/its-too-hot.groovy @@ -73,7 +73,8 @@ def temperatureHandler(evt) { // TODO: Send "Temperature back to normal" SMS, turn switch off } else { log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch" - send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:"F"}") + def tempScale = location.temperatureScale ?: "F" + send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}") switch1?.on() } } From b105d9d80ed9194d56c355b038ee72e4128cefe5 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Thu, 19 May 2016 13:58:44 -0400 Subject: [PATCH 13/14] DVCSMP-1775 - Call poll() every 5 minutes to reduce duplicated calls (#902) * DVCSMP-1775 - Call poll() every 5 minutes to reduce duplicated calls Call poll() every 5 minutes instead of discovery() to reduce duplicated getActivityList() call. * Better error logging --- .../logitech-harmony-connect.groovy | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy index 5cdfdff..83b6cc6 100644 --- a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy +++ b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy @@ -339,7 +339,7 @@ def initialize() { state.aux = 0 if (selectedhubs || selectedactivities) { addDevice() - runEvery5Minutes("discovery") + runEvery5Minutes("poll") } } @@ -394,9 +394,9 @@ def discovery() { } } catch (java.net.SocketTimeoutException e) { log.warn "Connection to the hub timed out. Please restart the hub and try again." - state.resethub = true + state.resethub = true } catch (e) { - log.warn "Hostname in certificate didn't match. Please try again later." + log.info "Logitech Harmony - Error: $e" } return null } @@ -474,7 +474,7 @@ def activity(dni,mode) { def poll() { // GET THE LIST OF ACTIVITIES if (state.HarmonyAccessToken) { - getActivityList() + getActivityList() def Params = [auth: state.HarmonyAccessToken] def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}" try { @@ -520,14 +520,17 @@ def poll() { return "Poll completed $map - $state.hubs" } } catch (groovyx.net.http.HttpResponseException e) { - if (e.statusCode == 401) { // token is expired - state.remove("HarmonyAccessToken") - return "Harmony Access token has expired" - } - } catch(Exception e) { - log.trace e - } - } + if (e.statusCode == 401) { // token is expired + state.remove("HarmonyAccessToken") + log.warn "Harmony Access token has expired" + } + } catch (java.net.SocketTimeoutException e) { + log.warn "Connection to the hub timed out. Please restart the hub and try again." + state.resethub = true + } catch (e) { + log.info "Logitech Harmony - Error: $e" + } + } } From a21f9f177c3722e4495597779e6ebc4e40d32c33 Mon Sep 17 00:00:00 2001 From: Will Price Date: Fri, 20 May 2016 14:22:19 -0500 Subject: [PATCH 14/14] MSA-1285: Updated submission, already reviewed, just cleaning up the pull request. --- .../simple-control.src/simple-control.groovy | 150 +++++++++++++++--- 1 file changed, 124 insertions(+), 26 deletions(-) diff --git a/smartapps/roomieremote-roomieconnect/simple-control.src/simple-control.groovy b/smartapps/roomieremote-roomieconnect/simple-control.src/simple-control.groovy index c2b3fdd..3b4bfaa 100644 --- a/smartapps/roomieremote-roomieconnect/simple-control.src/simple-control.groovy +++ b/smartapps/roomieremote-roomieconnect/simple-control.src/simple-control.groovy @@ -21,6 +21,12 @@ preferences() section("Allow Simple Control to Monitor and Control These Things...") { input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false + input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false + input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false + input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false + input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false + input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false + input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false } page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5) @@ -31,12 +37,17 @@ preferences() mappings { path("/devices") { + action: [ + GET: "getDevices" + ] + } + path("/:deviceType/devices") { action: [ GET: "getDevices", POST: "handleDevicesWithIDs" ] - } - path("/device/:id") { + } + path("/device/:deviceType/:id") { action: [ GET: "getDevice", POST: "updateDevice" @@ -93,33 +104,40 @@ def handleDevicesWithIDs() //log.debug("ids: ${ids}") def command = data?.command def arguments = data?.arguments + def type = params?.deviceType + //log.debug("device type: ${type}") if (command) { - def success = false + def statusCode = 404 //log.debug("command ${command}, arguments ${arguments}") for (devId in ids) { def device = allDevices.find { it.id == devId } - if (device) { - if (arguments) { + //log.debug("device: ${device}") + // Check if we have a device that responds to the specified command + if (validateCommand(device, type, command)) { + if (arguments) { device."$command"(*arguments) - } else { - device."$command"() - } - success = true + } + else { + device."$command"() + } + statusCode = 200 } else { - //log.debug("device not found ${devId}") + statusCode = 403 } } - - if (success) + def responseData = "{}" + switch (statusCode) { - render status: 200, data: "{}" - } - else - { - render status: 404, data: '{"msg": "Device not found"}' + case 403: + responseData = '{"msg": "Access denied. This command is not supported by current capability."}' + break + case 404: + responseData = '{"msg": "Device not found"}' + break } + render status: statusCode, data: responseData } else { @@ -164,25 +182,101 @@ def updateDevice() def data = request.JSON def command = data?.command def arguments = data?.arguments + def type = params?.deviceType + //log.debug("device type: ${type}") //log.debug("updateDevice, params: ${params}, request: ${data}") if (!command) { render status: 400, data: '{"msg": "command is required"}' } else { + def statusCode = 404 def device = allDevices.find { it.id == params.id } if (device) { - if (arguments) { - device."$command"(*arguments) + // Check if we have a device that responds to the specified command + if (validateCommand(device, type, command)) { + if (arguments) { + device."$command"(*arguments) + } + else { + device."$command"() + } + statusCode = 200 } else { - device."$command"() + statusCode = 403 } - render status: 204, data: "{}" - } else { - render status: 404, data: '{"msg": "Device not found"}' } + + def responseData = "{}" + switch (statusCode) + { + case 403: + responseData = '{"msg": "Access denied. This command is not supported by current capability."}' + break + case 404: + responseData = '{"msg": "Device not found"}' + break + } + render status: statusCode, data: responseData } } +/** + * Validating the command passed by the user based on capability. + * @return boolean + */ +def validateCommand(device, deviceType, command) { + //log.debug("validateCommand ${command}") + def capabilityCommands = getDeviceCapabilityCommands(device.capabilities) + //log.debug("capabilityCommands: ${capabilityCommands}") + def currentDeviceCapability = getCapabilityName(deviceType) + //log.debug("currentDeviceCapability: ${currentDeviceCapability}") + if (capabilityCommands[currentDeviceCapability]) { + return command in capabilityCommands[currentDeviceCapability] ? true : false + } else { + // Handling other device types here, which don't accept commands + httpError(400, "Bad request.") + } +} + +/** + * Need to get the attribute name to do the lookup. Only + * doing it for the device types which accept commands + * @return attribute name of the device type + */ +def getCapabilityName(type) { + switch(type) { + case "switches": + return "Switch" + case "locks": + return "Lock" + case "thermostats": + return "Thermostat" + case "doorControls": + return "Door Control" + case "colorControls": + return "Color Control" + case "musicPlayers": + return "Music Player" + case "switchLevels": + return "Switch Level" + default: + return type + } +} + +/** + * Constructing the map over here of + * supported commands by device capability + * @return a map of device capability -> supported commands + */ +def getDeviceCapabilityCommands(deviceCapabilities) { + def map = [:] + deviceCapabilities.collect { + map[it.name] = it.commands.collect{ it.name.toString() } + } + return map +} + def listSubscriptions() { //log.debug "listSubscriptions()" @@ -361,7 +455,13 @@ def agentDiscovery(params=[:]) } section("Allow Simple Control to Monitor and Control These Things...") { - input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false + input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false + input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false + input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false + input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false + input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false + input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false + input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false } } } @@ -672,5 +772,3 @@ def List getRealHubFirmwareVersions() } - -