mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
78 Commits
MSA-1255-1
...
PROD_2016.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8b039dda | ||
|
|
98d7829d1a | ||
|
|
532afd7336 | ||
|
|
ac7f1a0c66 | ||
|
|
34107f935e | ||
|
|
fd549631e6 | ||
|
|
417c246d61 | ||
|
|
0f1781c02e | ||
|
|
4ce6ee0890 | ||
|
|
f8050a5cd5 | ||
|
|
d44dac448b | ||
|
|
67c20abcba | ||
|
|
d56e132896 | ||
|
|
009ec2539d | ||
|
|
ff0860cbe1 | ||
|
|
ecfb99974b | ||
|
|
aa3a18f421 | ||
|
|
c2f18a91be | ||
|
|
fc6b14b85e | ||
|
|
0c1208928f | ||
|
|
02d9963fab | ||
|
|
f131fb71cf | ||
|
|
9c27ed6cb7 | ||
|
|
35edaa19c7 | ||
|
|
dc09201866 | ||
|
|
6afcbf8f70 | ||
|
|
2894d52efa | ||
|
|
a21f9f177c | ||
|
|
02f968b8cb | ||
|
|
b105d9d80e | ||
|
|
9bfad5d6a4 | ||
|
|
85a335d365 | ||
|
|
40e6778e31 | ||
|
|
feba6643a6 | ||
|
|
49c893771d | ||
|
|
d73f4c2ded | ||
|
|
13a056ec8d | ||
|
|
bd0ccd0c21 | ||
|
|
d34508c19d | ||
|
|
a8e118fe83 | ||
|
|
072cc066b6 | ||
|
|
17562c96ae | ||
|
|
f55452d1c6 | ||
|
|
14b5fd41b8 | ||
|
|
0f4d7bd520 | ||
|
|
a95fbf2612 | ||
|
|
0f60ca61cb | ||
|
|
293a73136e | ||
|
|
3b56fb4a2f | ||
|
|
26a0f6f939 | ||
|
|
8de3276ce6 | ||
|
|
f77c5810c6 | ||
|
|
9fc5f14dd7 | ||
|
|
55d7a4a263 | ||
|
|
cca1eccce6 | ||
|
|
56eef9cf22 | ||
|
|
b33d621696 | ||
|
|
45b78eff8d | ||
|
|
a133406b6e | ||
|
|
bd62962ee1 | ||
|
|
f1e54c8a5c | ||
|
|
a5da182bf4 | ||
|
|
a4a48fddd2 | ||
|
|
2bd18859b9 | ||
|
|
32f8d2d944 | ||
|
|
e1de599668 | ||
|
|
0e01cbed06 | ||
|
|
566425c531 | ||
|
|
bf476940e9 | ||
|
|
8821c68e9c | ||
|
|
27c05f4e5b | ||
|
|
69ae9973da | ||
|
|
e7448e7908 | ||
|
|
973c16f088 | ||
|
|
e278a3b57d | ||
|
|
b05d956d95 | ||
|
|
d17cadc4c7 | ||
|
|
91c01dc643 |
@@ -14,7 +14,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
//@Deprecated: Moved to ZLL Dimmer Bulb
|
||||
metadata {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
@@ -25,7 +24,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -138,6 +138,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ metadata {
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
||||
}
|
||||
simulator {
|
||||
//battery
|
||||
|
||||
@@ -37,9 +37,6 @@ metadata {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
|
||||
@@ -38,9 +38,6 @@ metadata {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
|
||||
@@ -33,9 +33,6 @@ metadata {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
}
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
|
||||
@@ -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"
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -75,9 +70,6 @@ metadata {
|
||||
def parse(String description) {
|
||||
//log.trace description
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||
{
|
||||
@@ -132,7 +124,6 @@ def off() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Power Meter"
|
||||
@@ -25,9 +25,6 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet"
|
||||
@@ -81,9 +78,6 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
@@ -124,7 +118,6 @@ def on() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
|
||||
@@ -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 {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
|
||||
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) {
|
||||
valueTile("currentColor", "device.color") {
|
||||
state "default", label: '${currentValue}'
|
||||
state "color", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
||||
@@ -41,6 +41,13 @@ def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setting color: $value"
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex) }
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
|
||||
}
|
||||
|
||||
def setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
|
||||
@@ -39,7 +39,7 @@ metadata {
|
||||
}
|
||||
|
||||
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "range", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
|
||||
|
||||
@@ -41,17 +41,17 @@ metadata {
|
||||
|
||||
// standard flat tile with only a label
|
||||
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "default", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff"
|
||||
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true
|
||||
}
|
||||
|
||||
// standard flat tile with icon and label
|
||||
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "default", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true
|
||||
}
|
||||
|
||||
// standard flat tile with only icon (Refreh text is IN the icon file)
|
||||
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
}
|
||||
|
||||
// standard with defaultState = true
|
||||
@@ -74,19 +74,19 @@ metadata {
|
||||
|
||||
// utility tiles to fill the spaces
|
||||
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "default", label:''
|
||||
state "emptySmall", label:'', defaultState: true
|
||||
}
|
||||
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
|
||||
state "default", label:''
|
||||
state "emptyBigger", label:'', defaultState: true
|
||||
}
|
||||
|
||||
// multi-line text (explicit newlines)
|
||||
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
|
||||
state "default", label: '${currentValue}'
|
||||
state "multiLine", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
|
||||
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
|
||||
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
}
|
||||
|
||||
main("actionRings")
|
||||
|
||||
@@ -22,68 +22,68 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
valueTile("text", "device.text", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("longText", "device.longText", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("integer", "device.integer", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("pi", "device.pi", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("bgColor", "device.integer", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}', backgroundColor: "#e86d13"
|
||||
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#ff0000"],
|
||||
[value: 90, color: "#0000ff"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#333333"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#990000"],
|
||||
[value: 10, color: "#000099"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("noValue", "device.nada", width: 4, height: 2) {
|
||||
state "default", label:'${currentValue}'
|
||||
state "noval", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
|
||||
state "default", label: '${currentValue}'
|
||||
state "val", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
|
||||
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
|
||||
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
}
|
||||
|
||||
main("text")
|
||||
details([
|
||||
"text", "longText", "integer",
|
||||
"text", "longText", "integer",
|
||||
"integerFloat", "pi", "floatAsText",
|
||||
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
||||
"bgColorRangeConflict", "noValue",
|
||||
|
||||
@@ -39,15 +39,15 @@ metadata {
|
||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel"
|
||||
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true
|
||||
}
|
||||
tileAttribute("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "default", action:"switch level.setLevel"
|
||||
attributeState "level", action:"switch level.setLevel", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label:'${currentValue}', backgroundColors:[
|
||||
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
|
||||
[value: 0, color: "#ff0000"],
|
||||
[value: 20, color: "#ffff00"],
|
||||
[value: 40, color: "#00ff00"],
|
||||
@@ -69,34 +69,34 @@ metadata {
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ def installed() {
|
||||
sendEvent(name: "multilineText", value: "Line 1 YES\nLine 2 YES\nLine 3 NO")
|
||||
}
|
||||
|
||||
def parse() {
|
||||
def parse(String description) {
|
||||
// This is a simulated device. No incoming data to parse.
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,10 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
@@ -173,7 +173,6 @@ def setColor(value) {
|
||||
def reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
||||
//parent.poll()
|
||||
}
|
||||
|
||||
def setAdjustedColor(value) {
|
||||
@@ -189,7 +188,6 @@ def setAdjustedColor(value) {
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
//parent.manualRefresh()
|
||||
}
|
||||
|
||||
def adjustOutgoingHue(percent) {
|
||||
@@ -208,4 +206,3 @@ def adjustOutgoingHue(percent) {
|
||||
log.info "percent: $percent, adjusted: $adjusted"
|
||||
adjusted
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ metadata {
|
||||
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
||||
}
|
||||
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
|
||||
attributeState("default", action:"music Player.previousTrack")
|
||||
attributeState("status", action:"music Player.previousTrack", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.status", key: "NEXT_TRACK") {
|
||||
attributeState("default", action:"music Player.nextTrack")
|
||||
attributeState("status", action:"music Player.nextTrack", defaultState: true)
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState("level", action:"music Player.setLevel")
|
||||
@@ -50,7 +50,7 @@ metadata {
|
||||
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
||||
}
|
||||
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
||||
attributeState("default", label:"${currentValue}")
|
||||
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
||||
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
@@ -53,15 +53,16 @@ metadata {
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
@@ -79,15 +80,16 @@ metadata {
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF",
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
@@ -118,30 +120,30 @@ metadata {
|
||||
)
|
||||
}
|
||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'down', action:"tempDown"
|
||||
state "tempDown", label:'down', action:"tempDown", defaultState: true
|
||||
}
|
||||
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'up', action:"tempUp"
|
||||
state "tempUp", label:'up', action:"tempUp", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'down', action:"heatDown"
|
||||
state "heatDown", label:'down', action:"heatDown", defaultState: true
|
||||
}
|
||||
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'up', action:"heatUp"
|
||||
state "heatUp", label:'up', action:"heatUp", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'down', action:"coolDown"
|
||||
state "coolDown", label:'down', action:"coolDown", defaultState: true
|
||||
}
|
||||
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'up', action:"coolUp"
|
||||
state "coolUp", label:'up', action:"coolUp", defaultState: true
|
||||
}
|
||||
|
||||
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
|
||||
@@ -22,8 +22,9 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
|
||||
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019"
|
||||
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019", manufacturer: "Philips", model: "LWB006", deviceJoinName: "Philips Hue White"
|
||||
|
||||
@@ -95,11 +95,17 @@ def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
if (state.manufacturer != cmd.manufacturerName) {
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
}
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
|
||||
@@ -4,29 +4,33 @@
|
||||
import java.text.DecimalFormat
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
private apiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" }
|
||||
private getApiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" }
|
||||
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { "https://graph.api.smartthings.com" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { appSettings.serverUrl }
|
||||
private getShardUrl() { return getApiServerUrl() }
|
||||
private getCallbackUrl() { "${serverUrl}/oauth/callback" }
|
||||
private getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${shardUrl}" }
|
||||
|
||||
// Automatically generated. Make future change here.
|
||||
definition(
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
){
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -35,35 +39,52 @@ preferences {
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]}
|
||||
path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]}
|
||||
path("/auth"){action: [GET: "auth"]}
|
||||
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||
path("/oauth/callback") {action: [GET: "callback"]}
|
||||
}
|
||||
|
||||
def authPage() {
|
||||
log.debug "In authPage"
|
||||
if(canInstallLabs()) {
|
||||
def description = null
|
||||
|
||||
if (state.vendorAccessToken == null) {
|
||||
log.debug "About to create access token."
|
||||
def description
|
||||
def uninstallAllowed = false
|
||||
def oauthTokenProvided = false
|
||||
|
||||
createAccessToken()
|
||||
description = "Tap to enter Credentials."
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token."
|
||||
state.accessToken = createAccessToken()
|
||||
}
|
||||
|
||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) {
|
||||
section { href url:buildRedirectUrl("auth"), style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description }
|
||||
if (canInstallLabs()) {
|
||||
|
||||
def redirectUrl = getBuildRedirectUrl()
|
||||
log.debug "Redirect url = ${redirectUrl}"
|
||||
|
||||
if (state.authToken) {
|
||||
description = "Tap 'Next' to proceed"
|
||||
uninstallAllowed = true
|
||||
oauthTokenProvided = true
|
||||
} else {
|
||||
description = "Click to enter Credentials."
|
||||
}
|
||||
|
||||
if (!oauthTokenProvided) {
|
||||
log.debug "Show the login page"
|
||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||
section() {
|
||||
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
|
||||
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
|
||||
}
|
||||
}
|
||||
} else {
|
||||
description = "Tap 'Next' to proceed"
|
||||
|
||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
|
||||
section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description }
|
||||
log.debug "Show the devices page"
|
||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||
section() {
|
||||
input(name:"Devices", style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
|
||||
|
||||
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
|
||||
@@ -78,229 +99,175 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next
|
||||
}
|
||||
}
|
||||
|
||||
def auth() {
|
||||
redirect location: oauthInitUrl()
|
||||
}
|
||||
|
||||
def oauthInitUrl() {
|
||||
log.debug "In oauthInitUrl"
|
||||
|
||||
/* OAuth Step 1: Request access code with our client ID */
|
||||
|
||||
state.oauthInitState = UUID.randomUUID().toString()
|
||||
|
||||
def oauthParams = [ response_type: "code",
|
||||
client_id: getClientId(),
|
||||
state: state.oauthInitState,
|
||||
redirect_uri: buildRedirectUrl("receiveToken") ,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
return getVendorAuthPath() + toQueryString(oauthParams)
|
||||
}
|
||||
|
||||
def buildRedirectUrl(endPoint) {
|
||||
log.debug "In buildRedirectUrl"
|
||||
|
||||
return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}"
|
||||
}
|
||||
|
||||
def receiveToken() {
|
||||
log.debug "In receiveToken"
|
||||
|
||||
def oauthParams = [
|
||||
client_secret: getClientSecret(),
|
||||
response_type: "code",
|
||||
client_id: getClientId(),
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: buildRedirectUrl('receiveToken'),
|
||||
code: params.code,
|
||||
client_secret: getClientSecret(),
|
||||
state: state.oauthInitState,
|
||||
redirect_uri: getCallbackUrl(),
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
uri: tokenUrl,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
body: oauthParams,
|
||||
]
|
||||
|
||||
log.debug params
|
||||
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
||||
|
||||
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
|
||||
try {
|
||||
httpPost(params) { response ->
|
||||
log.debug response.data
|
||||
def slurper = new JsonSlurper();
|
||||
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
||||
}
|
||||
|
||||
response.data.each {key, value ->
|
||||
def data = slurper.parseText(key);
|
||||
log.debug "Data: $data"
|
||||
def callback() {
|
||||
log.debug "callback()>> params: $params, params.code ${params.code}"
|
||||
|
||||
state.vendorRefreshToken = data.refresh_token
|
||||
state.vendorAccessToken = data.access_token
|
||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
||||
return
|
||||
def code = params.code
|
||||
def oauthState = params.state
|
||||
|
||||
if (oauthState == state.oauthInitState) {
|
||||
|
||||
def tokenParams = [
|
||||
client_secret: getClientSecret(),
|
||||
client_id : getClientId(),
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: getCallbackUrl(),
|
||||
code: code,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
uri: tokenUrl,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
body: tokenParams
|
||||
]
|
||||
|
||||
log.debug "PARAMS: ${params}"
|
||||
|
||||
httpPost(params) { resp ->
|
||||
|
||||
def slurper = new JsonSlurper()
|
||||
|
||||
resp.data.each { key, value ->
|
||||
def data = slurper.parseText(key)
|
||||
|
||||
state.refreshToken = data.refresh_token
|
||||
state.authToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
log.debug "swapped token: $resp.data"
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "Error: $e"
|
||||
|
||||
// Handle success and failure here, and render stuff accordingly
|
||||
if (state.authToken) {
|
||||
success()
|
||||
} else {
|
||||
fail()
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error "callback() failed oauthState != state.oauthInitState"
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "State: $state"
|
||||
def success() {
|
||||
log.debug "in success"
|
||||
def message = """
|
||||
<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
|
||||
return
|
||||
def fail() {
|
||||
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 = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${getVendorName()} 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: 100%;
|
||||
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>We have located your """ + getVendorName() + """ account.</p>
|
||||
<p>Tap 'Done' to process your credentials.</p>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${getVendorName()} 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: 100%;
|
||||
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" />
|
||||
${message}
|
||||
</div>
|
||||
</body>
|
||||
</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() {
|
||||
log.debug "In refreshToken"
|
||||
|
||||
@@ -308,8 +275,8 @@ def refreshToken() {
|
||||
client_secret: getClientSecret(),
|
||||
client_id: getClientId(),
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: state.vendorRefreshToken
|
||||
]
|
||||
refresh_token: state.refreshToken
|
||||
]
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
@@ -318,7 +285,7 @@ def refreshToken() {
|
||||
body: oauthParams,
|
||||
]
|
||||
|
||||
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
|
||||
// OAuth Step 2: Request access token with our client Secret and OAuth "Code"
|
||||
try {
|
||||
httpPost(params) { response ->
|
||||
def slurper = new JsonSlurper();
|
||||
@@ -327,9 +294,9 @@ def refreshToken() {
|
||||
def data = slurper.parseText(key);
|
||||
log.debug "Data: $data"
|
||||
|
||||
state.vendorRefreshToken = data.refresh_token
|
||||
state.vendorAccessToken = data.access_token
|
||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
||||
state.refreshToken = data.refresh_token
|
||||
state.accessToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -338,9 +305,8 @@ def refreshToken() {
|
||||
log.debug "Error: $e"
|
||||
}
|
||||
|
||||
log.debug "State: $state"
|
||||
|
||||
if ( !state.vendorAccessToken ) { //We didn't get an access token
|
||||
// We didn't get an access token
|
||||
if ( !state.accessToken ) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -482,13 +448,13 @@ def listDevices() {
|
||||
}
|
||||
|
||||
def apiGet(String path, Map query, Closure callback) {
|
||||
if(now() >= state.vendorTokenExpires) {
|
||||
if(now() >= state.tokenExpires) {
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
query['access_token'] = state.vendorAccessToken
|
||||
query['access_token'] = state.accessToken
|
||||
def params = [
|
||||
uri: apiUrl(),
|
||||
uri: getApiUrl(),
|
||||
path: path,
|
||||
'query': query
|
||||
]
|
||||
|
||||
@@ -370,9 +370,7 @@ def parse_api_response(resp, message) {
|
||||
}
|
||||
}
|
||||
|
||||
def getServerUrl() {
|
||||
return "https://graph.api.smartthings.com"
|
||||
}
|
||||
def getServerUrl() { return getApiServerUrl() }
|
||||
|
||||
def debugEvent(message, displayEvent) {
|
||||
def results = [
|
||||
|
||||
@@ -21,6 +21,12 @@ preferences()
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
}
|
||||
|
||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
||||
@@ -31,12 +37,17 @@ preferences()
|
||||
|
||||
mappings {
|
||||
path("/devices") {
|
||||
action: [
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/:deviceType/devices") {
|
||||
action: [
|
||||
GET: "getDevices",
|
||||
POST: "handleDevicesWithIDs"
|
||||
]
|
||||
}
|
||||
path("/device/:id") {
|
||||
}
|
||||
path("/device/:deviceType/:id") {
|
||||
action: [
|
||||
GET: "getDevice",
|
||||
POST: "updateDevice"
|
||||
@@ -93,33 +104,40 @@ def handleDevicesWithIDs()
|
||||
//log.debug("ids: ${ids}")
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
if (command)
|
||||
{
|
||||
def success = false
|
||||
def statusCode = 404
|
||||
//log.debug("command ${command}, arguments ${arguments}")
|
||||
for (devId in ids)
|
||||
{
|
||||
def device = allDevices.find { it.id == devId }
|
||||
if (device) {
|
||||
if (arguments) {
|
||||
//log.debug("device: ${device}")
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
success = true
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
} else {
|
||||
//log.debug("device not found ${devId}")
|
||||
statusCode = 403
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
def responseData = "{}"
|
||||
switch (statusCode)
|
||||
{
|
||||
render status: 200, data: "{}"
|
||||
}
|
||||
else
|
||||
{
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
case 403:
|
||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
break
|
||||
case 404:
|
||||
responseData = '{"msg": "Device not found"}'
|
||||
break
|
||||
}
|
||||
render status: statusCode, data: responseData
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -164,25 +182,101 @@ def updateDevice()
|
||||
def data = request.JSON
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
|
||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def statusCode = 404
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
} else {
|
||||
device."$command"()
|
||||
statusCode = 403
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
|
||||
def responseData = "{}"
|
||||
switch (statusCode)
|
||||
{
|
||||
case 403:
|
||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
break
|
||||
case 404:
|
||||
responseData = '{"msg": "Device not found"}'
|
||||
break
|
||||
}
|
||||
render status: statusCode, data: responseData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
//log.debug("validateCommand ${command}")
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
//log.debug("capabilityCommands: ${capabilityCommands}")
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
case "thermostats":
|
||||
return "Thermostat"
|
||||
case "doorControls":
|
||||
return "Door Control"
|
||||
case "colorControls":
|
||||
return "Color Control"
|
||||
case "musicPlayers":
|
||||
return "Music Player"
|
||||
case "switchLevels":
|
||||
return "Switch Level"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions()
|
||||
{
|
||||
//log.debug "listSubscriptions()"
|
||||
@@ -361,7 +455,13 @@ def agentDiscovery(params=[:])
|
||||
}
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,5 +772,3 @@ def List getRealHubFirmwareVersions()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -297,8 +297,8 @@ private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting).time
|
||||
def stop = timeToday(ending).time
|
||||
def start = timeToday(starting, location.timeZone).time
|
||||
def stop = timeToday(ending, location.timeZone).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
|
||||
@@ -323,6 +323,8 @@ private getDeviceType(hueType) {
|
||||
return "Hue Bulb"
|
||||
else if (hueType?.equalsIgnoreCase("Color Light"))
|
||||
return "Hue Bloom"
|
||||
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
|
||||
return "Hue White Ambiance Bulb"
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
|
||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||
} else {
|
||||
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
|
||||
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||
def tempScale = location.temperatureScale ?: "F"
|
||||
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||
switch1?.on()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
|
||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||
} else {
|
||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||
def tempScale = location.temperatureScale ?: "F"
|
||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||
switch1?.on()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ definition(
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -192,7 +191,7 @@ def getSmartThingsClientId() {
|
||||
return "pREqugabRetre4EstetherufrePumamExucrEHuc"
|
||||
}
|
||||
|
||||
def getServerUrl() { appSettings.serverUrl }
|
||||
def getServerUrl() { getApiServerUrl() }
|
||||
|
||||
def buildRedirectUrl()
|
||||
{
|
||||
|
||||
@@ -339,7 +339,7 @@ def initialize() {
|
||||
state.aux = 0
|
||||
if (selectedhubs || selectedactivities) {
|
||||
addDevice()
|
||||
runEvery5Minutes("discovery")
|
||||
runEvery5Minutes("poll")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,9 +394,9 @@ def discovery() {
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||
state.resethub = true
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.warn "Hostname in certificate didn't match. Please try again later."
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -474,7 +474,7 @@ def activity(dni,mode) {
|
||||
def poll() {
|
||||
// GET THE LIST OF ACTIVITIES
|
||||
if (state.HarmonyAccessToken) {
|
||||
getActivityList()
|
||||
getActivityList()
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||
try {
|
||||
@@ -520,14 +520,17 @@ def poll() {
|
||||
return "Poll completed $map - $state.hubs"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
return "Harmony Access token has expired"
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony Access token has expired"
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -107,8 +107,8 @@ mappings {
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks",
|
||||
PUT: "updateLock",
|
||||
POST: "updateLock"
|
||||
PUT: "updateLocks",
|
||||
POST: "updateLocks"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
@@ -442,31 +442,87 @@ def executePhrase() {
|
||||
}
|
||||
|
||||
private void updateAll(devices) {
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
devices."$command"()
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device)
|
||||
{
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
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) {
|
||||
|
||||
@@ -273,7 +273,7 @@ def scheduleCheck(evt) {
|
||||
else {
|
||||
if(people){
|
||||
//don't turn off lights if anyone is home
|
||||
if(someoneIsHome()){
|
||||
if(someoneIsHome){
|
||||
log.debug("Stopping Check for Light")
|
||||
unschedule()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user