mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 21:02:56 +00:00
Merge pull request #975 from SmartThingsCommunity/staging
Rolling up staging to production
This commit is contained in:
@@ -37,9 +37,6 @@ metadata {
|
|||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
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") {
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
attributeState "color", action:"setAdjustedColor"
|
attributeState "color", action:"setAdjustedColor"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ metadata {
|
|||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
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") {
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
attributeState "color", action:"setAdjustedColor"
|
attributeState "color", action:"setAdjustedColor"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ metadata {
|
|||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
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)") {
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Hue White Ambiance Bulb
|
||||||
|
*
|
||||||
|
* Philips Hue Type "Color Temperature Light"
|
||||||
|
*
|
||||||
|
* Author: SmartThings
|
||||||
|
*/
|
||||||
|
|
||||||
|
// for the UI
|
||||||
|
metadata {
|
||||||
|
// Automatically generated. Make future change here.
|
||||||
|
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
capability "Switch Level"
|
||||||
|
capability "Actuator"
|
||||||
|
capability "Color Temperature"
|
||||||
|
capability "Switch"
|
||||||
|
capability "Refresh"
|
||||||
|
|
||||||
|
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:"#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", range:"(0..100)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||||
|
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "colorTemperature", label: '${currentValue} K'
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("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", "refresh"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse events into attributes
|
||||||
|
def parse(description) {
|
||||||
|
log.debug "parse() - $description"
|
||||||
|
def results = []
|
||||||
|
|
||||||
|
def map = description
|
||||||
|
if (description instanceof String) {
|
||||||
|
log.debug "Hue Ambience 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 setLevel(percent) {
|
||||||
|
log.debug "Executing 'setLevel'"
|
||||||
|
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 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()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -19,11 +19,6 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
|
|
||||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
|
||||||
attribute "heartbeat", "string"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -75,9 +70,6 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
//log.trace 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?.startsWith("catchall:")) {
|
||||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||||
{
|
{
|
||||||
@@ -132,7 +124,6 @@ def off() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
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} 6 0", "delay 500",
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// 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 "Actuator"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Power Meter"
|
capability "Power Meter"
|
||||||
@@ -25,9 +25,6 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
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", 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: "3200-Sgb", deviceJoinName: "Outlet"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", 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) {
|
def parse(String description) {
|
||||||
log.debug "description is $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)
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
|
|
||||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||||
@@ -124,7 +118,6 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
|
||||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Smartsense Motion Sensor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health]($health)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||||
|
* **Motion Sensor** - can detect motion
|
||||||
|
* **Battery** - defines device uses a battery
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
A Category C2 motion sensor that has 120min check-in interval
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
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 "Motion Sensor"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
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 "Three Axis"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||||
|
|
||||||
metadata {
|
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 "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
metadata {
|
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 "Configuration"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
|
|||||||
42
devicetypes/smartthings/tile-ux/README.md
Normal file
42
devicetypes/smartthings/tile-ux/README.md
Normal file
@@ -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.
|
||||||
@@ -22,7 +22,7 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
valueTile("currentColor", "device.color") {
|
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) {
|
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
||||||
@@ -41,6 +41,13 @@ def parse(String description) {
|
|||||||
log.debug "Parsing '${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) {
|
def setSaturation(percent) {
|
||||||
log.debug "Executing 'setSaturation'"
|
log.debug "Executing 'setSaturation'"
|
||||||
sendEvent(name: "saturation", value: percent)
|
sendEvent(name: "saturation", value: percent)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
|
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)") {
|
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
|
||||||
|
|||||||
@@ -41,17 +41,17 @@ metadata {
|
|||||||
|
|
||||||
// standard flat tile with only a label
|
// standard flat tile with only a label
|
||||||
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
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
|
// standard flat tile with icon and label
|
||||||
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
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)
|
// standard flat tile with only icon (Refreh text is IN the icon file)
|
||||||
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
|
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
|
// standard with defaultState = true
|
||||||
@@ -74,19 +74,19 @@ metadata {
|
|||||||
|
|
||||||
// utility tiles to fill the spaces
|
// utility tiles to fill the spaces
|
||||||
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
|
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") {
|
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
|
||||||
state "default", label:''
|
state "emptyBigger", label:'', defaultState: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// multi-line text (explicit newlines)
|
// multi-line text (explicit newlines)
|
||||||
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
|
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) {
|
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")
|
main("actionRings")
|
||||||
|
|||||||
@@ -22,68 +22,68 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
valueTile("text", "device.text", width: 2, height: 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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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: 10, color: "#ff0000"],
|
||||||
[value: 90, color: "#0000ff"]
|
[value: 90, color: "#0000ff"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
||||||
state "default", label:'${currentValue}', backgroundColors: [
|
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||||
[value: 10, color: "#333333"]
|
[value: 10, color: "#333333"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
|
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: "#990000"],
|
||||||
[value: 10, color: "#000099"]
|
[value: 10, color: "#000099"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("noValue", "device.nada", width: 4, height: 2) {
|
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) {
|
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) {
|
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")
|
main("text")
|
||||||
details([
|
details([
|
||||||
"text", "longText", "integer",
|
"text", "longText", "integer",
|
||||||
"integerFloat", "pi", "floatAsText",
|
"integerFloat", "pi", "floatAsText",
|
||||||
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
||||||
"bgColorRangeConflict", "noValue",
|
"bgColorRangeConflict", "noValue",
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ metadata {
|
|||||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
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") {
|
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) {
|
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
|
||||||
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "default", label:'${currentValue}', backgroundColors:[
|
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
|
||||||
[value: 0, color: "#ff0000"],
|
[value: 0, color: "#ff0000"],
|
||||||
[value: 20, color: "#ffff00"],
|
[value: 20, color: "#ffff00"],
|
||||||
[value: 40, color: "#00ff00"],
|
[value: 40, color: "#00ff00"],
|
||||||
@@ -69,34 +69,34 @@ metadata {
|
|||||||
}
|
}
|
||||||
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
||||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
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") {
|
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) {
|
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
|
||||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
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") {
|
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) {
|
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
|
||||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
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") {
|
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) {
|
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
|
||||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
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") {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
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) {
|
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"])
|
main(["switch"])
|
||||||
@@ -173,7 +173,6 @@ def setColor(value) {
|
|||||||
def reset() {
|
def reset() {
|
||||||
log.debug "Executing 'reset'"
|
log.debug "Executing 'reset'"
|
||||||
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
||||||
//parent.poll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setAdjustedColor(value) {
|
def setAdjustedColor(value) {
|
||||||
@@ -189,7 +188,6 @@ def setAdjustedColor(value) {
|
|||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
//parent.manualRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def adjustOutgoingHue(percent) {
|
def adjustOutgoingHue(percent) {
|
||||||
@@ -208,4 +206,3 @@ def adjustOutgoingHue(percent) {
|
|||||||
log.info "percent: $percent, adjusted: $adjusted"
|
log.info "percent: $percent, adjusted: $adjusted"
|
||||||
adjusted
|
adjusted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ metadata {
|
|||||||
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
||||||
}
|
}
|
||||||
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
|
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") {
|
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") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState("level", action:"music Player.setLevel")
|
attributeState("level", action:"music Player.setLevel")
|
||||||
@@ -50,7 +50,7 @@ metadata {
|
|||||||
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
||||||
}
|
}
|
||||||
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
||||||
attributeState("default", label:"${currentValue}")
|
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
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") {
|
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
attributeState("VALUE_UP", action: "tempUp")
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
attributeState("VALUE_DOWN", action: "tempDown")
|
||||||
}
|
}
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
|
||||||
}
|
}
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
attributeState("idle", backgroundColor:"#44b621")
|
||||||
@@ -53,15 +53,16 @@ metadata {
|
|||||||
attributeState("auto", label:'${name}')
|
attributeState("auto", label:'${name}')
|
||||||
}
|
}
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
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") {
|
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) {
|
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
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") {
|
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
attributeState("VALUE_UP", action: "tempUp")
|
||||||
@@ -79,15 +80,16 @@ metadata {
|
|||||||
attributeState("auto", label:'${name}')
|
attributeState("auto", label:'${name}')
|
||||||
}
|
}
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
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") {
|
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) {
|
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF",
|
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
|
||||||
backgroundColors:[
|
backgroundColors:[
|
||||||
[value: 31, color: "#153591"],
|
[value: 31, color: "#153591"],
|
||||||
[value: 44, color: "#1e9cbb"],
|
[value: 44, color: "#1e9cbb"],
|
||||||
@@ -118,30 +120,30 @@ metadata {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
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") {
|
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") {
|
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
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") {
|
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") {
|
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
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") {
|
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") {
|
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* Gidjit Hub
|
||||||
|
*
|
||||||
|
* Copyright 2016 Matthew Page
|
||||||
|
*
|
||||||
|
* 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: "Gidjit Hub",
|
||||||
|
namespace: "com.gidjit.smartthings.hub",
|
||||||
|
author: "Matthew Page",
|
||||||
|
description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment",
|
||||||
|
category: "Convenience",
|
||||||
|
iconUrl: "http://www.gidjit.com/appicon.png",
|
||||||
|
iconX2Url: "http://www.gidjit.com/appicon@2x.png",
|
||||||
|
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
||||||
|
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
|
||||||
|
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||||
|
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||||
|
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||||
|
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mappings {
|
||||||
|
path("/structureinfo") {
|
||||||
|
action: [
|
||||||
|
GET: "structureInfo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/helloactions") {
|
||||||
|
action: [
|
||||||
|
GET: "helloActions"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/helloactions/:label") {
|
||||||
|
action: [
|
||||||
|
PUT: "executeAction"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path("/switch/:id/:command") {
|
||||||
|
action: [
|
||||||
|
PUT: "updateSwitch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path("/thermostat/:id/:command") {
|
||||||
|
action: [
|
||||||
|
PUT: "updateThermostat"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path("/windowshade/:id/:command") {
|
||||||
|
action: [
|
||||||
|
PUT: "updateWindowShade"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/acquiredata/:id") {
|
||||||
|
action: [
|
||||||
|
GET: "acquiredata"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
// subscribe to attributes, devices, locations, etc.
|
||||||
|
}
|
||||||
|
def helloActions() {
|
||||||
|
def actions = location.helloHome?.getPhrases()*.label
|
||||||
|
if(!actions) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
def executeAction() {
|
||||||
|
def actions = location.helloHome?.getPhrases()*.label
|
||||||
|
def a = actions?.find() { it == params.label }
|
||||||
|
if (!a) {
|
||||||
|
httpError(400, "invalid label $params.label")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
location.helloHome?.execute(params.label)
|
||||||
|
}
|
||||||
|
/* this is the primary function called to query at the structure and its devices */
|
||||||
|
def structureInfo() { //list all devices
|
||||||
|
def list = [:]
|
||||||
|
def currId = location.id
|
||||||
|
list[currId] = [:]
|
||||||
|
list[currId].name = location.name
|
||||||
|
list[currId].id = location.id
|
||||||
|
list[currId].temperatureScale = location.temperatureScale
|
||||||
|
list[currId].devices = [:]
|
||||||
|
|
||||||
|
def setValues = {
|
||||||
|
if (params.brief) {
|
||||||
|
return [id: it.id, name: it.displayName]
|
||||||
|
}
|
||||||
|
def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect {
|
||||||
|
"$it.name"
|
||||||
|
}, suppAttributes: it.supportedAttributes.collect {
|
||||||
|
"$it.name"
|
||||||
|
}, suppCommands: it.supportedCommands.collect {
|
||||||
|
"$it.name"
|
||||||
|
}]
|
||||||
|
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
switches?.each {
|
||||||
|
list[currId].devices[it.id] = setValues(it)
|
||||||
|
}
|
||||||
|
thermostats?.each {
|
||||||
|
list[currId].devices[it.id] = setValues(it)
|
||||||
|
}
|
||||||
|
windowShades?.each {
|
||||||
|
list[currId].devices[it.id] = setValues(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
}
|
||||||
|
/* This function returns all of the current values of the specified Devices attributes */
|
||||||
|
def acquiredata() {
|
||||||
|
def resp = [:]
|
||||||
|
if (!params.id) {
|
||||||
|
httpError(400, "invalid id $params.id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?:
|
||||||
|
thermostats.find() { it.id == params.id }
|
||||||
|
|
||||||
|
if (!dev) {
|
||||||
|
httpError(400, "invalid id $params.id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def att = dev.supportedAttributes
|
||||||
|
att.each {
|
||||||
|
resp[it.name] = dev.currentValue("$it.name")
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSwitch() {
|
||||||
|
// use the built-in request object to get the command parameter
|
||||||
|
def command = params.command
|
||||||
|
def sw = switches.find() { it.id == params.id }
|
||||||
|
if (!sw) {
|
||||||
|
httpError(400, "invalid id $params.id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
if ( sw.currentSwitch != "on" ) {
|
||||||
|
sw.on()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "off":
|
||||||
|
if ( sw.currentSwitch != "off" ) {
|
||||||
|
sw.off()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(400, "$command is not a valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void updateThermostat() {
|
||||||
|
// use the built-in request object to get the command parameter
|
||||||
|
def command = params.command
|
||||||
|
def therm = thermostats.find() { it.id == params.id }
|
||||||
|
if (!therm || !command) {
|
||||||
|
httpError(400, "invalid id $params.id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def passComm = [
|
||||||
|
"off",
|
||||||
|
"heat",
|
||||||
|
"emergencyHeat",
|
||||||
|
"cool",
|
||||||
|
"fanOn",
|
||||||
|
"fanAuto",
|
||||||
|
"fanCirculate",
|
||||||
|
"auto"
|
||||||
|
|
||||||
|
]
|
||||||
|
def passNumParamComm = [
|
||||||
|
"setHeatingSetpoint",
|
||||||
|
"setCoolingSetpoint",
|
||||||
|
]
|
||||||
|
def passStringParamComm = [
|
||||||
|
"setThermostatMode",
|
||||||
|
"setThermostatFanMode",
|
||||||
|
]
|
||||||
|
if (command in passComm) {
|
||||||
|
therm."$command"()
|
||||||
|
} else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) {
|
||||||
|
therm."$command"(Float.parseFloat(params.p1))
|
||||||
|
} else if (command in passStringParamComm && params.p1) {
|
||||||
|
therm."$command"(params.p1)
|
||||||
|
} else {
|
||||||
|
httpError(400, "$command is not a valid command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateWindowShade() {
|
||||||
|
// use the built-in request object to get the command parameter
|
||||||
|
def command = params.command
|
||||||
|
def ws = windowShades.find() { it.id == params.id }
|
||||||
|
if (!ws || !command) {
|
||||||
|
httpError(400, "invalid id $params.id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def passComm = [
|
||||||
|
"open",
|
||||||
|
"close",
|
||||||
|
"presetPosition",
|
||||||
|
]
|
||||||
|
if (command in passComm) {
|
||||||
|
ws."$command"()
|
||||||
|
} else {
|
||||||
|
httpError(400, "$command is not a valid command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: implement event handlers
|
||||||
@@ -4,29 +4,33 @@
|
|||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
|
|
||||||
private apiUrl() { "https://api.netatmo.com" }
|
private getApiUrl() { "https://api.netatmo.com" }
|
||||||
private getVendorName() { "netatmo" }
|
private getVendorName() { "netatmo" }
|
||||||
private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" }
|
private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" }
|
||||||
private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" }
|
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" }
|
||||||
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
|
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
|
||||||
private getClientId() { appSettings.clientId }
|
private getClientId() { appSettings.clientId }
|
||||||
private getClientSecret() { appSettings.clientSecret }
|
private getClientSecret() { appSettings.clientSecret }
|
||||||
private getServerUrl() { "https://graph.api.smartthings.com" }
|
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.
|
// Automatically generated. Make future change here.
|
||||||
definition(
|
definition(
|
||||||
name: "Netatmo (Connect)",
|
name: "Netatmo (Connect)",
|
||||||
namespace: "dianoga",
|
namespace: "dianoga",
|
||||||
author: "Brian Steere",
|
author: "Brian Steere",
|
||||||
description: "Netatmo Integration",
|
description: "Netatmo Integration",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
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",
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||||
oauth: true,
|
oauth: true,
|
||||||
singleInstance: true
|
singleInstance: true
|
||||||
){
|
){
|
||||||
appSetting "clientId"
|
appSetting "clientId"
|
||||||
appSetting "clientSecret"
|
appSetting "clientSecret"
|
||||||
|
appSetting "serverUrl"
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -35,35 +39,52 @@ preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mappings {
|
mappings {
|
||||||
path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]}
|
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||||
path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]}
|
path("/oauth/callback") {action: [GET: "callback"]}
|
||||||
path("/auth"){action: [GET: "auth"]}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def authPage() {
|
def authPage() {
|
||||||
log.debug "In authPage"
|
log.debug "In authPage"
|
||||||
if(canInstallLabs()) {
|
|
||||||
def description = null
|
|
||||||
|
|
||||||
if (state.vendorAccessToken == null) {
|
def description
|
||||||
log.debug "About to create access token."
|
def uninstallAllowed = false
|
||||||
|
def oauthTokenProvided = false
|
||||||
|
|
||||||
createAccessToken()
|
if (!state.accessToken) {
|
||||||
description = "Tap to enter Credentials."
|
log.debug "About to create access token."
|
||||||
|
state.accessToken = createAccessToken()
|
||||||
|
}
|
||||||
|
|
||||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) {
|
if (canInstallLabs()) {
|
||||||
section { href url:buildRedirectUrl("auth"), style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description }
|
|
||||||
|
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 {
|
} else {
|
||||||
description = "Tap 'Next' to proceed"
|
log.debug "Show the devices page"
|
||||||
|
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
|
section() {
|
||||||
section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description }
|
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.
|
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"."""
|
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() {
|
def oauthInitUrl() {
|
||||||
log.debug "In oauthInitUrl"
|
log.debug "In oauthInitUrl"
|
||||||
|
|
||||||
/* OAuth Step 1: Request access code with our client ID */
|
|
||||||
|
|
||||||
state.oauthInitState = UUID.randomUUID().toString()
|
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 = [
|
def oauthParams = [
|
||||||
client_secret: getClientSecret(),
|
response_type: "code",
|
||||||
client_id: getClientId(),
|
client_id: getClientId(),
|
||||||
grant_type: "authorization_code",
|
client_secret: getClientSecret(),
|
||||||
redirect_uri: buildRedirectUrl('receiveToken'),
|
state: state.oauthInitState,
|
||||||
code: params.code,
|
redirect_uri: getCallbackUrl(),
|
||||||
scope: "read_station"
|
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" */
|
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
||||||
try {
|
}
|
||||||
httpPost(params) { response ->
|
|
||||||
log.debug response.data
|
|
||||||
def slurper = new JsonSlurper();
|
|
||||||
|
|
||||||
response.data.each {key, value ->
|
def callback() {
|
||||||
def data = slurper.parseText(key);
|
log.debug "callback()>> params: $params, params.code ${params.code}"
|
||||||
log.debug "Data: $data"
|
|
||||||
|
|
||||||
state.vendorRefreshToken = data.refresh_token
|
def code = params.code
|
||||||
state.vendorAccessToken = data.access_token
|
def oauthState = params.state
|
||||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
|
||||||
return
|
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 = """
|
||||||
|
<p>We have located your """ + getVendorName() + """ account.</p>
|
||||||
|
<p>Tap 'Done' to continue to Devices.</p>
|
||||||
|
"""
|
||||||
|
connectionStatus(message)
|
||||||
|
}
|
||||||
|
|
||||||
if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install
|
def fail() {
|
||||||
return
|
log.debug "in fail"
|
||||||
|
def message = """
|
||||||
|
<p>The connection could not be established!</p>
|
||||||
|
<p>Click 'Done' to return to the menu.</p>
|
||||||
|
"""
|
||||||
|
connectionStatus(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
def connectionStatus(message, redirectUrl = null) {
|
||||||
|
def redirectHtml = ""
|
||||||
|
if (redirectUrl) {
|
||||||
|
redirectHtml = """
|
||||||
|
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
||||||
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */
|
|
||||||
|
|
||||||
def html = """
|
def html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>${getVendorName()} Connection</title>
|
<title>${getVendorName()} Connection</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
font-family: 'Swiss 721 W01 Thin';
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Swiss 721 W01 Light';
|
font-family: 'Swiss 721 W01 Light';
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
/*background: #eee;*/
|
/*background: #eee;*/
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
img:nth-child(2) {
|
img:nth-child(2) {
|
||||||
margin: 0 30px;
|
margin: 0 30px;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
font-family: 'Swiss 721 W01 Thin';
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
p:last-child {
|
p:last-child {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
span {
|
span {
|
||||||
font-family: 'Swiss 721 W01 Light';
|
font-family: 'Swiss 721 W01 Light';
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
||||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
||||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
||||||
<p>We have located your """ + getVendorName() + """ account.</p>
|
${message}
|
||||||
<p>Tap 'Done' to process your credentials.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
render contentType: 'text/html', data: html
|
render contentType: 'text/html', data: html
|
||||||
}
|
}
|
||||||
|
|
||||||
def receivedToken() {
|
|
||||||
log.debug "In receivedToken"
|
|
||||||
|
|
||||||
def html = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Withings Connection</title>
|
|
||||||
<style type="text/css">
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Swiss 721 W01 Light';
|
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 560px;
|
|
||||||
padding: 40px;
|
|
||||||
/*background: #eee;*/
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
img:nth-child(2) {
|
|
||||||
margin: 0 30px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-size: 2.2em;
|
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
|
||||||
text-align: center;
|
|
||||||
color: #666666;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
p:last-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
span {
|
|
||||||
font-family: 'Swiss 721 W01 Light';
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
|
||||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
|
||||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
|
||||||
<p>Tap 'Done' to continue to Devices.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
render contentType: 'text/html', data: html
|
|
||||||
}
|
|
||||||
|
|
||||||
// "
|
|
||||||
|
|
||||||
def refreshToken() {
|
def refreshToken() {
|
||||||
log.debug "In refreshToken"
|
log.debug "In refreshToken"
|
||||||
|
|
||||||
@@ -308,8 +275,8 @@ def refreshToken() {
|
|||||||
client_secret: getClientSecret(),
|
client_secret: getClientSecret(),
|
||||||
client_id: getClientId(),
|
client_id: getClientId(),
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token: state.vendorRefreshToken
|
refresh_token: state.refreshToken
|
||||||
]
|
]
|
||||||
|
|
||||||
def tokenUrl = getVendorTokenPath()
|
def tokenUrl = getVendorTokenPath()
|
||||||
def params = [
|
def params = [
|
||||||
@@ -318,7 +285,7 @@ def refreshToken() {
|
|||||||
body: oauthParams,
|
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 {
|
try {
|
||||||
httpPost(params) { response ->
|
httpPost(params) { response ->
|
||||||
def slurper = new JsonSlurper();
|
def slurper = new JsonSlurper();
|
||||||
@@ -327,9 +294,9 @@ def refreshToken() {
|
|||||||
def data = slurper.parseText(key);
|
def data = slurper.parseText(key);
|
||||||
log.debug "Data: $data"
|
log.debug "Data: $data"
|
||||||
|
|
||||||
state.vendorRefreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.vendorAccessToken = data.access_token
|
state.accessToken = data.access_token
|
||||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,9 +305,8 @@ def refreshToken() {
|
|||||||
log.debug "Error: $e"
|
log.debug "Error: $e"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "State: $state"
|
// We didn't get an access token
|
||||||
|
if ( !state.accessToken ) {
|
||||||
if ( !state.vendorAccessToken ) { //We didn't get an access token
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,13 +448,13 @@ def listDevices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def apiGet(String path, Map query, Closure callback) {
|
def apiGet(String path, Map query, Closure callback) {
|
||||||
if(now() >= state.vendorTokenExpires) {
|
if(now() >= state.tokenExpires) {
|
||||||
refreshToken();
|
refreshToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
query['access_token'] = state.vendorAccessToken
|
query['access_token'] = state.accessToken
|
||||||
def params = [
|
def params = [
|
||||||
uri: apiUrl(),
|
uri: getApiUrl(),
|
||||||
path: path,
|
path: path,
|
||||||
'query': query
|
'query': query
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -370,9 +370,7 @@ def parse_api_response(resp, message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getServerUrl() {
|
def getServerUrl() { return getApiServerUrl() }
|
||||||
return "https://graph.api.smartthings.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
def debugEvent(message, displayEvent) {
|
def debugEvent(message, displayEvent) {
|
||||||
def results = [
|
def results = [
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ preferences()
|
|||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
||||||
@@ -31,12 +37,17 @@ preferences()
|
|||||||
|
|
||||||
mappings {
|
mappings {
|
||||||
path("/devices") {
|
path("/devices") {
|
||||||
|
action: [
|
||||||
|
GET: "getDevices"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/:deviceType/devices") {
|
||||||
action: [
|
action: [
|
||||||
GET: "getDevices",
|
GET: "getDevices",
|
||||||
POST: "handleDevicesWithIDs"
|
POST: "handleDevicesWithIDs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/device/:id") {
|
path("/device/:deviceType/:id") {
|
||||||
action: [
|
action: [
|
||||||
GET: "getDevice",
|
GET: "getDevice",
|
||||||
POST: "updateDevice"
|
POST: "updateDevice"
|
||||||
@@ -93,33 +104,40 @@ def handleDevicesWithIDs()
|
|||||||
//log.debug("ids: ${ids}")
|
//log.debug("ids: ${ids}")
|
||||||
def command = data?.command
|
def command = data?.command
|
||||||
def arguments = data?.arguments
|
def arguments = data?.arguments
|
||||||
|
def type = params?.deviceType
|
||||||
|
//log.debug("device type: ${type}")
|
||||||
if (command)
|
if (command)
|
||||||
{
|
{
|
||||||
def success = false
|
def statusCode = 404
|
||||||
//log.debug("command ${command}, arguments ${arguments}")
|
//log.debug("command ${command}, arguments ${arguments}")
|
||||||
for (devId in ids)
|
for (devId in ids)
|
||||||
{
|
{
|
||||||
def device = allDevices.find { it.id == devId }
|
def device = allDevices.find { it.id == devId }
|
||||||
if (device) {
|
//log.debug("device: ${device}")
|
||||||
if (arguments) {
|
// Check if we have a device that responds to the specified command
|
||||||
|
if (validateCommand(device, type, command)) {
|
||||||
|
if (arguments) {
|
||||||
device."$command"(*arguments)
|
device."$command"(*arguments)
|
||||||
} else {
|
}
|
||||||
device."$command"()
|
else {
|
||||||
}
|
device."$command"()
|
||||||
success = true
|
}
|
||||||
|
statusCode = 200
|
||||||
} else {
|
} else {
|
||||||
//log.debug("device not found ${devId}")
|
statusCode = 403
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
def responseData = "{}"
|
||||||
if (success)
|
switch (statusCode)
|
||||||
{
|
{
|
||||||
render status: 200, data: "{}"
|
case 403:
|
||||||
}
|
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||||
else
|
break
|
||||||
{
|
case 404:
|
||||||
render status: 404, data: '{"msg": "Device not found"}'
|
responseData = '{"msg": "Device not found"}'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
render status: statusCode, data: responseData
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -164,25 +182,101 @@ def updateDevice()
|
|||||||
def data = request.JSON
|
def data = request.JSON
|
||||||
def command = data?.command
|
def command = data?.command
|
||||||
def arguments = data?.arguments
|
def arguments = data?.arguments
|
||||||
|
def type = params?.deviceType
|
||||||
|
//log.debug("device type: ${type}")
|
||||||
|
|
||||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
||||||
if (!command) {
|
if (!command) {
|
||||||
render status: 400, data: '{"msg": "command is required"}'
|
render status: 400, data: '{"msg": "command is required"}'
|
||||||
} else {
|
} else {
|
||||||
|
def statusCode = 404
|
||||||
def device = allDevices.find { it.id == params.id }
|
def device = allDevices.find { it.id == params.id }
|
||||||
if (device) {
|
if (device) {
|
||||||
if (arguments) {
|
// Check if we have a device that responds to the specified command
|
||||||
device."$command"(*arguments)
|
if (validateCommand(device, type, command)) {
|
||||||
|
if (arguments) {
|
||||||
|
device."$command"(*arguments)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device."$command"()
|
||||||
|
}
|
||||||
|
statusCode = 200
|
||||||
} else {
|
} 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()
|
def listSubscriptions()
|
||||||
{
|
{
|
||||||
//log.debug "listSubscriptions()"
|
//log.debug "listSubscriptions()"
|
||||||
@@ -361,7 +455,13 @@ def agentDiscovery(params=[:])
|
|||||||
}
|
}
|
||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
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()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -297,8 +297,8 @@ private getTimeOk() {
|
|||||||
def result = true
|
def result = true
|
||||||
if (starting && ending) {
|
if (starting && ending) {
|
||||||
def currTime = now()
|
def currTime = now()
|
||||||
def start = timeToday(starting).time
|
def start = timeToday(starting, location.timeZone).time
|
||||||
def stop = timeToday(ending).time
|
def stop = timeToday(ending, location.timeZone).time
|
||||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||||
}
|
}
|
||||||
log.trace "timeOk = $result"
|
log.trace "timeOk = $result"
|
||||||
|
|||||||
@@ -323,6 +323,8 @@ private getDeviceType(hueType) {
|
|||||||
return "Hue Bulb"
|
return "Hue Bulb"
|
||||||
else if (hueType?.equalsIgnoreCase("Color Light"))
|
else if (hueType?.equalsIgnoreCase("Color Light"))
|
||||||
return "Hue Bloom"
|
return "Hue Bloom"
|
||||||
|
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
|
||||||
|
return "Hue White Ambiance Bulb"
|
||||||
else
|
else
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
|
|||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
|
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()
|
switch1?.on()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
|
|||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
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()
|
switch1?.on()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ definition(
|
|||||||
) {
|
) {
|
||||||
appSetting "clientId"
|
appSetting "clientId"
|
||||||
appSetting "clientSecret"
|
appSetting "clientSecret"
|
||||||
appSetting "serverUrl"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -192,7 +191,7 @@ def getSmartThingsClientId() {
|
|||||||
return "pREqugabRetre4EstetherufrePumamExucrEHuc"
|
return "pREqugabRetre4EstetherufrePumamExucrEHuc"
|
||||||
}
|
}
|
||||||
|
|
||||||
def getServerUrl() { appSettings.serverUrl }
|
def getServerUrl() { getApiServerUrl() }
|
||||||
|
|
||||||
def buildRedirectUrl()
|
def buildRedirectUrl()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ def initialize() {
|
|||||||
state.aux = 0
|
state.aux = 0
|
||||||
if (selectedhubs || selectedactivities) {
|
if (selectedhubs || selectedactivities) {
|
||||||
addDevice()
|
addDevice()
|
||||||
runEvery5Minutes("discovery")
|
runEvery5Minutes("poll")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,9 +394,9 @@ def discovery() {
|
|||||||
}
|
}
|
||||||
} catch (java.net.SocketTimeoutException e) {
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||||
state.resethub = true
|
state.resethub = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn "Hostname in certificate didn't match. Please try again later."
|
log.info "Logitech Harmony - Error: $e"
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -474,7 +474,7 @@ def activity(dni,mode) {
|
|||||||
def poll() {
|
def poll() {
|
||||||
// GET THE LIST OF ACTIVITIES
|
// GET THE LIST OF ACTIVITIES
|
||||||
if (state.HarmonyAccessToken) {
|
if (state.HarmonyAccessToken) {
|
||||||
getActivityList()
|
getActivityList()
|
||||||
def Params = [auth: state.HarmonyAccessToken]
|
def Params = [auth: state.HarmonyAccessToken]
|
||||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||||
try {
|
try {
|
||||||
@@ -520,14 +520,17 @@ def poll() {
|
|||||||
return "Poll completed $map - $state.hubs"
|
return "Poll completed $map - $state.hubs"
|
||||||
}
|
}
|
||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
if (e.statusCode == 401) { // token is expired
|
if (e.statusCode == 401) { // token is expired
|
||||||
state.remove("HarmonyAccessToken")
|
state.remove("HarmonyAccessToken")
|
||||||
return "Harmony Access token has expired"
|
log.warn "Harmony Access token has expired"
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
log.trace 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ mappings {
|
|||||||
path("/locks") {
|
path("/locks") {
|
||||||
action: [
|
action: [
|
||||||
GET: "listLocks",
|
GET: "listLocks",
|
||||||
PUT: "updateLock",
|
PUT: "updateLocks",
|
||||||
POST: "updateLock"
|
POST: "updateLocks"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/locks/:id") {
|
path("/locks/:id") {
|
||||||
@@ -442,31 +442,87 @@ def executePhrase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAll(devices) {
|
private void updateAll(devices) {
|
||||||
|
def type = params.param1
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
if (command)
|
if (!devices) {
|
||||||
{
|
httpError(404, "Devices not found")
|
||||||
command = command.toLowerCase()
|
}
|
||||||
devices."$command"()
|
if (command){
|
||||||
|
devices.each { device ->
|
||||||
|
executeCommand(device, type, command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(devices) {
|
private void update(devices) {
|
||||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||||
//def command = request.JSON?.command
|
def type = params.param1
|
||||||
def command = params.command
|
def command = request.JSON?.command
|
||||||
if (command)
|
def device = devices?.find { it.id == params.id }
|
||||||
{
|
|
||||||
command = command.toLowerCase()
|
if (!device) {
|
||||||
def device = devices.find { it.id == params.id }
|
|
||||||
if (!device)
|
|
||||||
{
|
|
||||||
httpError(404, "Device not found")
|
httpError(404, "Device not found")
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
device."$command"()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
executeCommand(device, type, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validating the command passed by the user based on capability.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
def validateCommand(device, deviceType, command) {
|
||||||
|
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||||
|
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||||
|
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"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and executes the command
|
||||||
|
* on the device or devices
|
||||||
|
*/
|
||||||
|
def executeCommand(device, type, command) {
|
||||||
|
if (validateCommand(device, type, command)) {
|
||||||
|
device."$command"()
|
||||||
|
} else {
|
||||||
|
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private show(devices, type) {
|
private show(devices, type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user