mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-25 13:04:10 +00:00
Compare commits
1 Commits
MSA-2065-1
...
MSA-2038-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e9370a612 |
18
build.gradle
18
build.gradle
@@ -9,7 +9,7 @@ apply plugin: 'smartthings-slack'
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.12"
|
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.11"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -19,7 +19,7 @@ buildscript {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ repositories {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +51,10 @@ sourceSets {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.3'
|
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
||||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.12'
|
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
||||||
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
smartappsCompile 'smartthings:appengine-common:0.1.9'
|
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
||||||
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
||||||
smartappsCompile 'org.grails:grails-web:2.3.11'
|
smartappsCompile 'org.grails:grails-web:2.3.11'
|
||||||
smartappsCompile 'org.json:json:20140107'
|
smartappsCompile 'org.json:json:20140107'
|
||||||
@@ -74,19 +74,19 @@ slackSendMessage {
|
|||||||
String username
|
String username
|
||||||
switch (branch) {
|
switch (branch) {
|
||||||
case 'master':
|
case 'master':
|
||||||
username = 'DEV'
|
username = 'Hickory'
|
||||||
iconUrl = wolverine
|
iconUrl = wolverine
|
||||||
color = '#35D0F2'
|
color = '#35D0F2'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
||||||
break
|
break
|
||||||
case 'staging':
|
case 'staging':
|
||||||
username = 'STG'
|
username = 'Dickory'
|
||||||
iconUrl = beach
|
iconUrl = beach
|
||||||
color = '#FFDE20'
|
color = '#FFDE20'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
||||||
break
|
break
|
||||||
case 'production':
|
case 'production':
|
||||||
username = 'PRD'
|
username = 'Dock'
|
||||||
iconUrl = drinks
|
iconUrl = drinks
|
||||||
color = '#FF1D23'
|
color = '#FF1D23'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Aeon Labs Key Fob
|
|
||||||
|
|
||||||
Cloud Execution
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Aeon Labs Key Fob](http://aeotec.com/z-wave-key-fob-remote-control)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Actuator** - represents device has commands
|
|
||||||
* **Button** - represents a device with one or more buttons
|
|
||||||
* **Holdable Button** - represents a device with one or more holdable buttons
|
|
||||||
* **Configuration** - allows for configuration of devices
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
Aeon Key Fob is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the Aeon Labs Key Fob from SmartThings can be found in the following link:
|
|
||||||
* [Aeotec Key Fob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202294120-Aeon-Labs-Key-Fob)
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import groovy.json.JsonOutput
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
@@ -20,7 +19,6 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
|
||||||
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
|
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
|
||||||
@@ -133,9 +131,6 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
// Arrival sensors only goes OFFLINE when Hub is off
|
|
||||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
|
||||||
|
|
||||||
def zwMap = getZwaveInfo()
|
def zwMap = getZwaveInfo()
|
||||||
def buttons = 4 // Default for Key Fob
|
def buttons = 4 // Default for Key Fob
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Aeon Minimote
|
|
||||||
|
|
||||||
Cloud Execution
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Aeotec Minimote](http://aeotec.com/small-z-wave-remote-control)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Actuator** - represents device has commands
|
|
||||||
* **Button** - represents a device with one or more buttons
|
|
||||||
* **Holdable Button** - represents a device with one or more holdable buttons
|
|
||||||
* **Configuration** - allows for configuration of devices
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
Aeon Minimote is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the Aeotec Minimote from SmartThings can be found in the following link:
|
|
||||||
* [Aeotec Minimote Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202087904-Aeotec-Minimote)
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import groovy.json.JsonOutput
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
@@ -19,7 +18,6 @@ metadata {
|
|||||||
capability "Holdable Button"
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B"
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B"
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons
|
||||||
@@ -121,7 +119,5 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
// Arrival sensors only goes OFFLINE when Hub is off
|
|
||||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
|
||||||
sendEvent(name: "numberOfButtons", value: 4)
|
sendEvent(name: "numberOfButtons", value: 4)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,21 +300,15 @@ def setColor(value) {
|
|||||||
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if(value.hue) {
|
sendEvent(name: "hue", value: value.hue, displayed: false)
|
||||||
sendEvent(name: "hue", value: value.hue, displayed: false)
|
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||||
}
|
sendEvent(name: "color", value: value.hex, displayed: false)
|
||||||
if(value.saturation) {
|
if (value.level) {
|
||||||
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
sendEvent(name: "level", value: value.level)
|
||||||
}
|
}
|
||||||
if(value.hex?.trim()) {
|
if (value.switch) {
|
||||||
sendEvent(name: "color", value: value.hex, displayed: false)
|
sendEvent(name: "switch", value: value.switch)
|
||||||
}
|
}
|
||||||
if (value.level) {
|
|
||||||
sendEvent(name: "level", value: value.level)
|
|
||||||
}
|
|
||||||
if (value.switch?.trim()) {
|
|
||||||
sendEvent(name: "switch", value: value.switch)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendRGB(value.rh, value.gh, value.bh)
|
sendRGB(value.rh, value.gh, value.bh)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ metadata {
|
|||||||
|
|
||||||
// tile definitions
|
// tile definitions
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
||||||
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
|
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
|
||||||
@@ -48,8 +48,8 @@ metadata {
|
|||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "contact"
|
main "valve"
|
||||||
details(["contact","refresh"])
|
details(["valve","refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# ZigBee Button
|
|
||||||
|
|
||||||
Cloud Execution
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [OSRAM LIGHTIFY Dimming Switch](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
|
|
||||||
* [Iris Smart Button](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
|
|
||||||
* [Iris KeyFob](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Actuator** - It represents that a device has commands.
|
|
||||||
* **Battery** - It defines that the device has a battery
|
|
||||||
* **Button** - It defines that a device has one or more buttons
|
|
||||||
* **Holdable Button** - It defines that a device has one or more holdable buttons
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Sensor** - it represents that a Device has attributes.
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
|
||||||
|
|
||||||
* __722min__ checkInterval
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
|
||||||
It may also happen that you need to reset the device.
|
|
||||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
|
||||||
* [OSRAM LIGHTIFY Dimming Switch Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
|
|
||||||
* [Iris Smart Button Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
|
|
||||||
* [Iris KeyFob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)
|
|
||||||
@@ -24,7 +24,6 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -183,13 +182,6 @@ private Map parseNonIasButtonMessage(Map descMap){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Battery"
|
log.debug "Refreshing Battery"
|
||||||
|
|
||||||
@@ -198,8 +190,6 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 2 check-in misses from device (plus 2 mins lag time)
|
|
||||||
sendEvent(name: "checkInterval", value: 2 * 6 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
def cmds = []
|
def cmds = []
|
||||||
if (device.getDataValue("model") == "3450-L") {
|
if (device.getDataValue("model") == "3450-L") {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
||||||
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
@@ -58,8 +58,8 @@ metadata {
|
|||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["contact"])
|
main(["valve"])
|
||||||
details(["contact", "battery", "refresh"])
|
details(["valve", "battery", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,10 +195,7 @@ def registerDeviceChange() {
|
|||||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
||||||
// state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||||
// For now, we will only have one subscription endpoint per device
|
|
||||||
state.deviceSubscriptionMap.remove(deviceId)
|
|
||||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
/**
|
||||||
|
* JSON Complete API
|
||||||
|
*
|
||||||
|
* Copyright 2017 Paul Lovelace
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
definition(
|
||||||
|
name: "JSON Complete API",
|
||||||
|
namespace: "smartthings",
|
||||||
|
author: "Ashok Malhotra",
|
||||||
|
description: "API for JSON with complete set of devices",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%401.png",
|
||||||
|
iconX2Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%402.png",
|
||||||
|
iconX3Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%403.png",
|
||||||
|
oauth: true)
|
||||||
|
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
page(name: "copyConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
//When adding device groups, need to add here
|
||||||
|
def copyConfig() {
|
||||||
|
if (!state.accessToken) {
|
||||||
|
createAccessToken()
|
||||||
|
}
|
||||||
|
dynamicPage(name: "copyConfig", title: "Configure Devices", install:true, uninstall:true) {
|
||||||
|
section("Select devices to include in the /devices API call") {
|
||||||
|
paragraph "Version 0.5.5"
|
||||||
|
input "deviceList", "capability.refresh", title: "Most Devices", multiple: true, required: false
|
||||||
|
input "sensorList", "capability.sensor", title: "Sensor Devices", multiple: true, required: false
|
||||||
|
input "switchList", "capability.switch", title: "All Switches", multiple: true, required: false
|
||||||
|
//paragraph "Devices Selected: ${deviceList ? deviceList?.size() : 0}\nSensors Selected: ${sensorList ? sensorList?.size() : 0}\nSwitches Selected: ${switchList ? switchList?.size() : 0}"
|
||||||
|
}
|
||||||
|
section("Configure Pubnub") {
|
||||||
|
input "pubnubSubscribeKey", "text", title: "PubNub Subscription Key", multiple: false, required: false
|
||||||
|
input "pubnubPublishKey", "text", title: "PubNub Publish Key", multiple: false, required: false
|
||||||
|
input "subChannel", "text", title: "Channel (Can be anything)", multiple: false, required: false
|
||||||
|
}
|
||||||
|
section() {
|
||||||
|
paragraph "View this SmartApp's configuration to use it in other places."
|
||||||
|
href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/config?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
|
||||||
|
}
|
||||||
|
|
||||||
|
section() {
|
||||||
|
paragraph "View the JSON generated from the installed devices."
|
||||||
|
href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/devices?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Device Results", description:"View accessories JSON"
|
||||||
|
}
|
||||||
|
section() {
|
||||||
|
paragraph "Enter the name you would like shown in the smart app list"
|
||||||
|
label title:"SmartApp Label (optional)", required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def renderDevices() {
|
||||||
|
def deviceData = []
|
||||||
|
deviceList.each {
|
||||||
|
try {
|
||||||
|
deviceData << [name: it.displayName,
|
||||||
|
basename: it.name,
|
||||||
|
deviceid: it.id,
|
||||||
|
status: it.status,
|
||||||
|
manufacturerName: it.getManufacturerName(),
|
||||||
|
modelName: it.getModelName(),
|
||||||
|
lastTime: it.getLastActivity(),
|
||||||
|
capabilities: deviceCapabilityList(it),
|
||||||
|
commands: deviceCommandList(it),
|
||||||
|
attributes: deviceAttributeList(it)
|
||||||
|
]
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sensorList.each {
|
||||||
|
try {
|
||||||
|
deviceData << [name: it.displayName,
|
||||||
|
basename: it.name,
|
||||||
|
deviceid: it.id,
|
||||||
|
status: it.status,
|
||||||
|
manufacturerName: it.getManufacturerName(),
|
||||||
|
modelName: it.getModelName(),
|
||||||
|
lastTime: it.getLastActivity(),
|
||||||
|
capabilities: deviceCapabilityList(it),
|
||||||
|
commands: deviceCommandList(it),
|
||||||
|
attributes: deviceAttributeList(it)
|
||||||
|
]
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switchList.each {
|
||||||
|
try {
|
||||||
|
deviceData << [name: it.displayName,
|
||||||
|
basename: it.name,
|
||||||
|
deviceid: it.id,
|
||||||
|
status: it.status,
|
||||||
|
manufacturerName: it.getManufacturerName(),
|
||||||
|
modelName: it.getModelName(),
|
||||||
|
lastTime: it.getLastActivity(),
|
||||||
|
capabilities: deviceCapabilityList(it),
|
||||||
|
commands: deviceCommandList(it),
|
||||||
|
attributes: deviceAttributeList(it)
|
||||||
|
]
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceData
|
||||||
|
}
|
||||||
|
|
||||||
|
def findDevice(paramid) {
|
||||||
|
def device = deviceList.find { it.id == paramid }
|
||||||
|
if (device) return device
|
||||||
|
device = sensorList.find { it.id == paramid }
|
||||||
|
if (device) return device
|
||||||
|
device = switchList.find { it.id == paramid }
|
||||||
|
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
//No more individual device group definitions after here.
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
if(!state.accessToken) {
|
||||||
|
createAccessToken()
|
||||||
|
}
|
||||||
|
registerAll()
|
||||||
|
state.subscriptionRenewed = 0
|
||||||
|
subscribe(location, null, HubResponseEvent, [filterEvents:false])
|
||||||
|
log.debug "0.5.5"
|
||||||
|
}
|
||||||
|
|
||||||
|
def authError() {
|
||||||
|
[error: "Permission denied"]
|
||||||
|
}
|
||||||
|
def renderConfig() {
|
||||||
|
def configJson = new groovy.json.JsonOutput().toJson([
|
||||||
|
description: "JSON API",
|
||||||
|
platforms: [
|
||||||
|
[
|
||||||
|
platform: "SmartThings",
|
||||||
|
name: "SmartThings",
|
||||||
|
app_url: apiServerUrl("/api/smartapps/installations/"),
|
||||||
|
app_id: app.id,
|
||||||
|
access_token: state.accessToken
|
||||||
|
]
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
def configString = new groovy.json.JsonOutput().prettyPrint(configJson)
|
||||||
|
render contentType: "text/plain", data: configString
|
||||||
|
}
|
||||||
|
def renderLocation() {
|
||||||
|
[
|
||||||
|
latitude: location.latitude,
|
||||||
|
longitude: location.longitude,
|
||||||
|
mode: location.mode,
|
||||||
|
name: location.name,
|
||||||
|
temperature_scale: location.temperatureScale,
|
||||||
|
zip_code: location.zipCode,
|
||||||
|
hubIP: location.hubs[0].localIP,
|
||||||
|
smartapp_version: '0.5.5'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
def CommandReply(statusOut, messageOut) {
|
||||||
|
def replyData =
|
||||||
|
[
|
||||||
|
status: statusOut,
|
||||||
|
message: messageOut
|
||||||
|
]
|
||||||
|
|
||||||
|
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
|
||||||
|
render contentType: "application/json", data: replyJson
|
||||||
|
}
|
||||||
|
def deviceCommand() {
|
||||||
|
log.info("Command Request")
|
||||||
|
def device = findDevice(params.id)
|
||||||
|
def command = params.command
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
log.error("Device Not Found")
|
||||||
|
CommandReply("Failure", "Device Not Found")
|
||||||
|
} else if (!device.hasCommand(command)) {
|
||||||
|
log.error("Device "+device.displayName+" does not have the command "+command)
|
||||||
|
CommandReply("Failure", "Device "+device.displayName+" does not have the command "+command)
|
||||||
|
} else {
|
||||||
|
def value1 = request.JSON?.value1
|
||||||
|
def value2 = request.JSON?.value2
|
||||||
|
try {
|
||||||
|
if (value2) {
|
||||||
|
device."$command"(value1,value2)
|
||||||
|
} else if (value1) {
|
||||||
|
device."$command"(value1)
|
||||||
|
} else {
|
||||||
|
device."$command"()
|
||||||
|
}
|
||||||
|
log.info("Command Successful for Device "+device.displayName+", Command "+command)
|
||||||
|
CommandReply("Success", "Device "+device.displayName+", Command "+command)
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Error Occurred For Device "+device.displayName+", Command "+command)
|
||||||
|
CommandReply("Failure", "Error Occurred For Device "+device.displayName+", Command "+command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def deviceAttribute() {
|
||||||
|
def device = findDevice(params.id)
|
||||||
|
def attribute = params.attribute
|
||||||
|
if (!device) {
|
||||||
|
httpError(404, "Device not found")
|
||||||
|
} else {
|
||||||
|
def currentValue = device.currentValue(attribute)
|
||||||
|
[currentValue: currentValue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def deviceQuery() {
|
||||||
|
def device = findDevice(params.id)
|
||||||
|
if (!device) {
|
||||||
|
device = null
|
||||||
|
httpError(404, "Device not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
def jsonData =
|
||||||
|
[
|
||||||
|
name: device.displayName,
|
||||||
|
deviceid: device.id,
|
||||||
|
capabilities: deviceCapabilityList(device),
|
||||||
|
commands: deviceCommandList(device),
|
||||||
|
attributes: deviceAttributeList(device)
|
||||||
|
]
|
||||||
|
def resultJson = new groovy.json.JsonOutput().toJson(jsonData)
|
||||||
|
render contentType: "application/json", data: resultJson
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def deviceCapabilityList(device) {
|
||||||
|
def i=0
|
||||||
|
device.capabilities.collectEntries { capability->
|
||||||
|
[
|
||||||
|
(capability.name):1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def deviceCommandList(device) {
|
||||||
|
def i=0
|
||||||
|
device.supportedCommands.collectEntries { command->
|
||||||
|
[
|
||||||
|
(command.name): (command.arguments)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def deviceAttributeList(device) {
|
||||||
|
device.supportedAttributes.collectEntries { attribute->
|
||||||
|
try {
|
||||||
|
[
|
||||||
|
(attribute.name): device.currentValue(attribute.name)
|
||||||
|
]
|
||||||
|
} catch(e) {
|
||||||
|
[
|
||||||
|
(attribute.name): null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def getAllData() {
|
||||||
|
//Since we're about to send all of the data, we'll count this as a subscription renewal and clear out pending changes.
|
||||||
|
state.subscriptionRenewed = now()
|
||||||
|
state.devchanges = []
|
||||||
|
|
||||||
|
|
||||||
|
def deviceData =
|
||||||
|
[ location: renderLocation(),
|
||||||
|
deviceList: renderDevices() ]
|
||||||
|
def deviceJson = new groovy.json.JsonOutput().toJson(deviceData)
|
||||||
|
render contentType: "application/json", data: deviceJson
|
||||||
|
}
|
||||||
|
def startSubscription() {
|
||||||
|
//This simply registers the subscription.
|
||||||
|
state.subscriptionRenewed = now()
|
||||||
|
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
|
||||||
|
render contentType: "application/json", data: deviceJson
|
||||||
|
}
|
||||||
|
def endSubscription() {
|
||||||
|
//Because it takes too long to register for an api command, we don't actually unregister.
|
||||||
|
//We simply blank the devchanges and change the subscription renewal to two hours ago.
|
||||||
|
state.devchanges = []
|
||||||
|
state.subscriptionRenewed = 0
|
||||||
|
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
|
||||||
|
render contentType: "application/json", data: deviceJson
|
||||||
|
}
|
||||||
|
def registerAll() {
|
||||||
|
//This has to be done at startup because it takes too long for a normal command.
|
||||||
|
log.debug "Registering All Events"
|
||||||
|
state.devchanges = []
|
||||||
|
registerChangeHandler(deviceList)
|
||||||
|
registerChangeHandler(sensorList)
|
||||||
|
registerChangeHandler(switchList)
|
||||||
|
}
|
||||||
|
def registerChangeHandler(myList) {
|
||||||
|
myList.each { myDevice ->
|
||||||
|
def theAtts = myDevice.supportedAttributes
|
||||||
|
theAtts.each {att ->
|
||||||
|
subscribe(myDevice, att.name, changeHandler)
|
||||||
|
log.debug "Registering ${myDevice.displayName}.${att.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def changeHandler(evt) {
|
||||||
|
//Send to Pubnub if we need to.
|
||||||
|
if (pubnubPublishKey!=null) {
|
||||||
|
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
|
||||||
|
def changeJson = new groovy.json.JsonOutput().toJson(deviceData)
|
||||||
|
def changeData = URLEncoder.encode(changeJson)
|
||||||
|
def uri = "http://pubsub.pubnub.com/publish/${pubnubPublishKey}/${pubnubSubscribeKey}/0/${subChannel}/0/${changeData}"
|
||||||
|
log.debug "${uri}"
|
||||||
|
httpGet(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.directIP!="") {
|
||||||
|
//Send Using the Direct Mechanism
|
||||||
|
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
|
||||||
|
//How do I control the port?!?
|
||||||
|
log.debug "Sending Update to ${state.directIP}:${state.directPort}"
|
||||||
|
def result = new physicalgraph.device.HubAction(
|
||||||
|
method: "GET",
|
||||||
|
path: "/update",
|
||||||
|
headers: [
|
||||||
|
HOST: "${state.directIP}:${state.directPort}",
|
||||||
|
change_device: evt.deviceId,
|
||||||
|
change_attribute: evt.name,
|
||||||
|
change_value: evt.value,
|
||||||
|
change_date: evt.date
|
||||||
|
]
|
||||||
|
)
|
||||||
|
sendHubCommand(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only add to the state's devchanges if the endpoint has renewed in the last 10 minutes.
|
||||||
|
if (state.subscriptionRenewed>(now()-(1000*60*10))) {
|
||||||
|
if (evt.isStateChange()) {
|
||||||
|
state.devchanges << [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
|
||||||
|
}
|
||||||
|
} else if (state.subscriptionRenewed>0) { //Otherwise, clear it
|
||||||
|
log.debug "Endpoint Subscription Expired. No longer storing changes for devices."
|
||||||
|
state.devchanges=[]
|
||||||
|
state.subscriptionRenewed=0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def getChangeEvents() {
|
||||||
|
//Store the changes so we can swap it out very quickly and eliminate the possibility of losing any.
|
||||||
|
//This is mainly to make this thread safe because I'm willing to bet that a change event can fire
|
||||||
|
//while generating/sending the JSON.
|
||||||
|
def oldchanges = state.devchanges
|
||||||
|
state.devchanges=[]
|
||||||
|
state.subscriptionRenewed = now()
|
||||||
|
if (oldchanges.size()==0) {
|
||||||
|
def deviceJson = new groovy.json.JsonOutput().toJson([status: "None"])
|
||||||
|
render contentType: "application/json", data: deviceJson
|
||||||
|
} else {
|
||||||
|
def changeJson = new groovy.json.JsonOutput().toJson([status: "Success", attributes:oldchanges])
|
||||||
|
render contentType: "application/json", data: changeJson
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def enableDirectUpdates() {
|
||||||
|
log.debug("Command Request")
|
||||||
|
state.directIP = params.ip
|
||||||
|
state.directPort = params.port
|
||||||
|
log.debug("Trying ${state.directIP}:${state.directPort}")
|
||||||
|
def result = new physicalgraph.device.HubAction(
|
||||||
|
method: "GET",
|
||||||
|
path: "/initial",
|
||||||
|
headers: [
|
||||||
|
HOST: "${state.directIP}:${state.directPort}"
|
||||||
|
],
|
||||||
|
query: deviceData
|
||||||
|
)
|
||||||
|
sendHubCommand(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
def HubResponseEvent(evt) {
|
||||||
|
log.debug(evt.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
def locationHandler(evt) {
|
||||||
|
def description = evt.description
|
||||||
|
def hub = evt?.hubId
|
||||||
|
|
||||||
|
log.debug "cp desc: " + description
|
||||||
|
if (description.count(",") > 4)
|
||||||
|
{
|
||||||
|
def bodyString = new String(description.split(',')[5].split(":")[1].decodeBase64())
|
||||||
|
log.debug(bodyString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getSubscriptionService() {
|
||||||
|
def replyData =
|
||||||
|
[
|
||||||
|
pubnub_publishkey: pubnubPublishKey,
|
||||||
|
pubnub_subscribekey: pubnubSubscribeKey,
|
||||||
|
pubnub_channel: subChannel
|
||||||
|
]
|
||||||
|
|
||||||
|
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
|
||||||
|
render contentType: "application/json", data: replyJson
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
if (!params.access_token || (params.access_token && params.access_token != state.accessToken)) {
|
||||||
|
path("/devices") { action: [GET: "authError"] }
|
||||||
|
path("/config") { action: [GET: "authError"] }
|
||||||
|
path("/location") { action: [GET: "authError"] }
|
||||||
|
path("/:id/command/:command") { action: [POST: "authError"] }
|
||||||
|
path("/:id/query") { action: [GET: "authError"] }
|
||||||
|
path("/:id/attribute/:attribute") { action: [GET: "authError"] }
|
||||||
|
path("/subscribe") { action: [GET: "authError"] }
|
||||||
|
path("/getUpdates") { action: [GET: "authError"] }
|
||||||
|
path("/unsubscribe") { action: [GET: "authError"] }
|
||||||
|
path("/startDirect/:ip/:port") { action: [GET: "authError"] }
|
||||||
|
path("/getSubcriptionService") { action: [GET: "authError"] }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
path("/devices") { action: [GET: "getAllData"] }
|
||||||
|
path("/config") { action: [GET: "renderConfig"] }
|
||||||
|
path("/location") { action: [GET: "renderLocation"] }
|
||||||
|
path("/:id/command/:command") { action: [POST: "deviceCommand"] }
|
||||||
|
path("/:id/query") { action: [GET: "deviceQuery"] }
|
||||||
|
path("/:id/attribute/:attribute") { action: [GET: "deviceAttribute"] }
|
||||||
|
path("/subscribe") { action: [GET: "startSubscription"] }
|
||||||
|
path("/getUpdates") { action: [GET: "getChangeEvents"] }
|
||||||
|
path("/unsubscribe") { action: [GET: "endSubscription"] }
|
||||||
|
path("/startDirect/:ip/:port") { action: [GET: "enableDirectUpdates"] }
|
||||||
|
path("/getSubcriptionService") { action: [GET: "getSubscriptionService"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user