mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
1 Commits
PROD_2017.
...
MSA-2038-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e9370a612 |
18
build.gradle
18
build.gradle
@@ -9,7 +9,7 @@ apply plugin: 'smartthings-slack'
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.12"
|
||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.11"
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -19,7 +19,7 @@ buildscript {
|
||||
username smartThingsArtifactoryUserName
|
||||
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
|
||||
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 {
|
||||
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.3'
|
||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.12'
|
||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
||||
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.grails:grails-web:2.3.11'
|
||||
smartappsCompile 'org.json:json:20140107'
|
||||
@@ -74,19 +74,19 @@ slackSendMessage {
|
||||
String username
|
||||
switch (branch) {
|
||||
case 'master':
|
||||
username = 'DEV'
|
||||
username = 'Hickory'
|
||||
iconUrl = wolverine
|
||||
color = '#35D0F2'
|
||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
||||
break
|
||||
case 'staging':
|
||||
username = 'STG'
|
||||
username = 'Dickory'
|
||||
iconUrl = beach
|
||||
color = '#FFDE20'
|
||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
||||
break
|
||||
case 'production':
|
||||
username = 'PRD'
|
||||
username = 'Dock'
|
||||
iconUrl = drinks
|
||||
color = '#FF1D23'
|
||||
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
|
||||
*
|
||||
@@ -20,7 +19,6 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
|
||||
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
|
||||
@@ -133,9 +131,6 @@ def updated() {
|
||||
}
|
||||
|
||||
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 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
|
||||
*
|
||||
@@ -19,7 +18,6 @@ metadata {
|
||||
capability "Holdable Button"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
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
|
||||
@@ -121,7 +119,5 @@ def updated() {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ metadata {
|
||||
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
|
||||
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
|
||||
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
|
||||
state "auxheatonly", action:"switchMode", icon: "st.thermostat.emergency-heat"
|
||||
state "emergency heat", action:"switchMode", icon: "st.thermostat.emergency-heat" // emergency heat = auxHeatOnly
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||
@@ -156,49 +156,47 @@ void poll() {
|
||||
def generateEvent(Map results) {
|
||||
log.debug "parsing data $results"
|
||||
if(results) {
|
||||
results.each { name, value ->
|
||||
|
||||
def linkText = getLinkText(device)
|
||||
def supportedThermostatModes = []
|
||||
def thermostatMode = null
|
||||
|
||||
results.each { name, value ->
|
||||
def isChange = false
|
||||
def isDisplayed = true
|
||||
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name]
|
||||
|
||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
||||
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||
event << [value: sendValue, unit: temperatureScale]
|
||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||
if (value == true) {
|
||||
supportedThermostatModes << ((name == "auxHeatMode") ? "auxheatonly" : name - "Mode")
|
||||
}
|
||||
return // as we don't want to send this event here, proceed to next name/value pair
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="thermostatFanMode"){
|
||||
sendEvent(name: "supportedThermostatFanModes", value: fanModes(), displayed: false)
|
||||
event << [value: value.toString(), data:[supportedThermostatFanModes: fanModes()]]
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="humidity") {
|
||||
event << [value: value.toString(), displayed: false, unit: "%"]
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
||||
} else if (name == "deviceAlive") {
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event['isStateChange'] = isChange
|
||||
event['displayed'] = false
|
||||
} else if (name == "thermostatMode") {
|
||||
thermostatMode = value.toLowerCase()
|
||||
return // as we don't want to send this event here, proceed to next name/value pair
|
||||
def mode = value.toString()
|
||||
mode = (mode == "auxHeatOnly") ? "emergency heat" : mode
|
||||
isChange = isStateChange(device, name, mode)
|
||||
event << [value: mode, isStateChange: isChange, displayed: isDisplayed]
|
||||
} else {
|
||||
event << [value: value.toString()]
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
|
||||
}
|
||||
sendEvent(event)
|
||||
}
|
||||
if (state.supportedThermostatModes != supportedThermostatModes) {
|
||||
state.supportedThermostatModes = supportedThermostatModes
|
||||
sendEvent(name: "supportedThermostatModes", value: supportedThermostatModes, displayed: false)
|
||||
}
|
||||
if (thermostatMode) {
|
||||
sendEvent(name: "thermostatMode", value: thermostatMode, data:[supportedThermostatModes:state.supportedThermostatModes], linkText: linkText,
|
||||
descriptionText: getThermostatDescriptionText("thermostatMode", thermostatMode, linkText), handlerName: "thermostatMode")
|
||||
}
|
||||
generateSetpointEvent ()
|
||||
generateStatusEvent ()
|
||||
}
|
||||
@@ -324,7 +322,15 @@ void resumeProgram() {
|
||||
}
|
||||
|
||||
def modes() {
|
||||
return state.supportedThermostatModes
|
||||
if (state.modes) {
|
||||
log.debug "Modes = ${state.modes}"
|
||||
return state.modes
|
||||
}
|
||||
else {
|
||||
state.modes = parent.availableModes(this)
|
||||
log.debug "Modes = ${state.modes}"
|
||||
return state.modes
|
||||
}
|
||||
}
|
||||
|
||||
def fanModes() {
|
||||
@@ -407,13 +413,11 @@ def setThermostatFanMode(String mode) {
|
||||
}
|
||||
|
||||
def generateModeEvent(mode) {
|
||||
sendEvent(name: "thermostatMode", value: mode, data:[supportedThermostatModes: state.supportedThermostatModes],
|
||||
descriptionText: "$device.displayName is in ${mode} mode")
|
||||
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true)
|
||||
}
|
||||
|
||||
def generateFanModeEvent(fanMode) {
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, data:[supportedThermostatFanModes: fanModes()],
|
||||
descriptionText: "$device.displayName fan is in ${fanMode} mode")
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
|
||||
}
|
||||
|
||||
def generateOperatingStateEvent(operatingState) {
|
||||
@@ -449,14 +453,14 @@ def heat() {
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
auxheatonly()
|
||||
auxHeatOnly()
|
||||
}
|
||||
|
||||
def auxheatonly() {
|
||||
log.debug "auxheatonly()"
|
||||
def auxHeatOnly() {
|
||||
log.debug "auxHeatOnly = emergency heat"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode ("auxHeatOnly", deviceId))
|
||||
generateModeEvent("auxheatonly")
|
||||
generateModeEvent("emergency heat") // emergency heat = auxHeatOnly
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
def currentMode = device.currentState("thermostatMode")?.value
|
||||
@@ -589,7 +593,7 @@ def generateSetpointEvent() {
|
||||
} else if (mode == "off") {
|
||||
sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"displayThermostatSetpoint", "value":"Off", displayed: false)
|
||||
} else if (mode == "auxheatonly") {
|
||||
} else if (mode == "emergency heat") { // emergency heat = auxHeatOnly
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false)
|
||||
}
|
||||
@@ -628,7 +632,7 @@ void raiseSetpoint() {
|
||||
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
||||
|
||||
if ((mode == "heat" || mode == "auxheatonly") && targetvalue > maxHeatingSetpoint) {
|
||||
if ((mode == "heat" || mode == "emergency heat") && targetvalue > maxHeatingSetpoint) { // emergency heat = auxHeatOnly
|
||||
targetvalue = maxHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
||||
targetvalue = maxCoolingSetpoint
|
||||
@@ -674,7 +678,7 @@ void lowerSetpoint() {
|
||||
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
||||
|
||||
if ((mode == "heat" || mode == "auxheatonly") && targetvalue < minHeatingSetpoint) {
|
||||
if ((mode == "heat" || mode == "emergency heat") && targetvalue < minHeatingSetpoint) { // emergency heat = auxHeatOnly
|
||||
targetvalue = minHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
||||
targetvalue = minCoolingSetpoint
|
||||
@@ -715,7 +719,7 @@ void alterSetpoint(temp) {
|
||||
}
|
||||
|
||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||
if (mode == "heat" || mode == "auxheatonly"){
|
||||
if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly
|
||||
if (temp.value > coolingSetpoint){
|
||||
targetHeatingSetpoint = temp.value
|
||||
targetCoolingSetpoint = temp.value
|
||||
@@ -750,7 +754,7 @@ void alterSetpoint(temp) {
|
||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||
} else {
|
||||
log.error "Error alterSetpoint()"
|
||||
if (mode == "heat" || mode == "auxheatonly"){
|
||||
if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly
|
||||
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||
sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||
} else if (mode == "cool") {
|
||||
@@ -779,7 +783,7 @@ def generateStatusEvent() {
|
||||
log.debug "Cooling set point = ${coolingSetpoint}"
|
||||
log.debug "HVAC Mode = ${mode}"
|
||||
|
||||
if (mode == "heat" || mode == "auxheatonly") {
|
||||
if (mode == "heat") {
|
||||
if (temperature >= heatingSetpoint) {
|
||||
statusText = "Right Now: Idle"
|
||||
} else {
|
||||
@@ -802,6 +806,8 @@ def generateStatusEvent() {
|
||||
}
|
||||
} else if (mode == "off") {
|
||||
statusText = "Right Now: Off"
|
||||
} else if (mode == "emergency heat") { // emergency heat = auxHeatOnly
|
||||
statusText = "Emergency Heat"
|
||||
} else {
|
||||
statusText = "?"
|
||||
}
|
||||
|
||||
@@ -300,21 +300,15 @@ def setColor(value) {
|
||||
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
||||
}
|
||||
|
||||
if(value.hue) {
|
||||
sendEvent(name: "hue", value: value.hue, displayed: false)
|
||||
}
|
||||
if(value.saturation) {
|
||||
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||
}
|
||||
if(value.hex?.trim()) {
|
||||
sendEvent(name: "color", value: value.hex, displayed: false)
|
||||
}
|
||||
if (value.level) {
|
||||
sendEvent(name: "level", value: value.level)
|
||||
}
|
||||
if (value.switch?.trim()) {
|
||||
sendEvent(name: "switch", value: value.switch)
|
||||
}
|
||||
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.level) {
|
||||
sendEvent(name: "level", value: value.level)
|
||||
}
|
||||
if (value.switch) {
|
||||
sendEvent(name: "switch", value: value.switch)
|
||||
}
|
||||
|
||||
sendRGB(value.rh, value.gh, value.bh)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ metadata {
|
||||
|
||||
// tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
||||
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 "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"
|
||||
}
|
||||
|
||||
main "contact"
|
||||
details(["contact","refresh"])
|
||||
main "valve"
|
||||
details(["valve","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
capability "Outlet"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||
|
||||
@@ -38,8 +38,8 @@ metadata {
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
||||
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 "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"
|
||||
}
|
||||
|
||||
main(["contact"])
|
||||
details(["contact", "battery", "refresh"])
|
||||
main(["valve"])
|
||||
details(["valve", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,10 +195,7 @@ def registerDeviceChange() {
|
||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(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])
|
||||
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||
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