Compare commits

..

1 Commits

Author SHA1 Message Date
nanoheal
9e9370a612 MSA-2038: testing the iot devices 2017-06-13 21:25:15 -07:00
18 changed files with 520 additions and 674 deletions

View File

@@ -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.'

View File

@@ -1,464 +0,0 @@
/**
* Intermatic PE653 Pool Control System
*
* Copyright 2014 bigpunk6
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Don't use Cooper Lee's code (vTile_ms, ms_w_vts) he was working on a different implementation than me.
*
* Install my device type then use the Multi-Channel Controll App by SmartThings from the Marketplace under the More section.
*
*/
metadata {
definition (name: "Intermatic PE653 Pool Control System", author: "bigpunk6", namespace: "bigpunk6") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Temperature Measurement"
capability "Sensor"
capability "Zw Multichannel"
capability "Thermostat"
attribute "operationMode", "string"
attribute "firemanTimeout", "string"
attribute "temperatureOffsets", "string"
attribute "poolspaConfig", "string"
attribute "poolSetpoint", "string"
attribute "spaSetpoint", "string"
attribute "setPoolSetpoint", "string"
attribute "pool", "string"
attribute "setSpaSetpoint", "string"
attribute "spa", "string"
command "quickSetPool"
command "quickSetSpa"
fingerprint deviceId: "0x1001", inClusters: "0x91,0x73,0x72,0x86,0x81,0x60,0x70,0x85,0x25,0x27,0x43,0x31", outClusters: "0x82"
}
preferences {
input "operationMode1", "enum", title: "Boster/Cleaner Pump",
options:[1:"No",
2:"Uses Circuit-1",
3:"Variable Speed pump Speed-1",
4:"Variable Speed pump Speed-2",
5:"Variable Speed pump Speed-3",
6:"Variable Speed pump Speed-4"]
input "operationMode2", "enum", title: "Pump Type",
options:[0:"1 Speed Pump without Booster/Cleaner",
1:"1 Speed Pump with Booster/Cleaner",
2:"2 Speed Pump without Booster/Cleaner",
3:"2 Speed Pump with Booster/Cleaner"]
input "poolSpa1", "enum", title: "Pool or Spa", options:[0:"Pool",1:"Spa",2:"Both"]
input "fireman", "enum", title: "Fireman Timeout",
options:["255":"No heater installed",
"0":"No cool down period",
"1":"1 minute",
"2":"2 minute",
"3":"3 minute",
"4":"4 minute",
"5":"5 minute",
"6":"6 minute",
"7":"7 minute",
"8":"8 minute",
"9":"9 minute",
"10":"10 minute",
"11":"11 minute",
"12":"12 minute",
"13":"13 minute",
"14":"14 minute",
"15":"15 minute"]
input "tempOffsetwater", "number", title: "Water temperature offset", defaultValue: 0, required: true
input "tempOffsetair", "number",
title: "Air temperature offset - Sets the Offset of the air temerature for the add-on Thermometer in degrees Fahrenheit -20F to +20F", defaultValue: 0, required: true
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
reply "8E010101,delay 800,6007": "command: 6008, payload: 4004"
reply "8505": "command: 8506, payload: 02"
reply "59034002": "command: 5904, payload: 8102003101000000"
reply "6007": "command: 6008, payload: 0002"
reply "600901": "command: 600A, payload: 10002532"
reply "600902": "command: 600A, payload: 210031"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"temperature", type: "thermostat", width: 6, height: 4){
tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
attributeState "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 32, color: "#153591"],
[value: 54, color: "#1e9cbb"],
[value: 64, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 90, color: "#f1d801"],
[value: 98, color: "#d04e00"],
[value: 110, color: "#bc2323"]
]
}
tileAttribute ("device.poolSetpoint", key: "VALUE_CONTROL") {
attributeState "poolSetpoint", action:"quickSetPool"
}
tileAttribute ("device.spaSetpoint", key: "SECONDARY_CONTROL") {
attributeState "spaSetpoint", label:'Spa set to ${currentValue}°F'
}
}
controlTile("poolSliderControl", "device.poolSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(40..104)") {
state "setPoolSetpoint", action:"quickSetPool", backgroundColor:"#d04e00"
}
valueTile("poolSetpoint", "device.poolSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "pool", label:'${currentValue}° pool', backgroundColor:"#ffffff"
}
controlTile("spaSliderControl", "device.spaSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(40..104)") {
state "setSpaSetpoint", action:"quickSetSpa", backgroundColor: "#1e9cbb"
}
valueTile("spaSetpoint", "device.spaSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "spa", label:'${currentValue}° spa', backgroundColor:"#ffffff"
}
/*valueTile("temperature", "device.temperature") {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 32, color: "#153591"],
[value: 54, color: "#1e9cbb"],
[value: 64, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 90, color: "#f1d801"],
[value: 98, color: "#d04e00"],
[value: 110, color: "#bc2323"]
]
)
}*/
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main "temperature"
details(["temperature", "poolSliderControl", "poolSetpoint", "spaSliderControl", "spaSetpoint", "configure", "refresh"])
}
}
def parse(String description) {
def result = null
if (description.startsWith("Err")) {
log.warn "Error in Parse"
result = createEvent(descriptionText:description, isStateChange:true)
} else {
def cmd = zwave.parse(description, [0x20: 1, 0x25:1, 0x27:1, 0x31:1, 0x43:1, 0x60:3, 0x70:2, 0x81:1, 0x85:1, 0x86: 1, 0x73:1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
log.debug("'$description' parsed to $result")
return result
}
//Thermostat
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
log.debug "ThermostatModeReport $cmd"
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "pool"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_FURNACE:
map.value = "spa"
break
}
map.name = "thermostatMode"
map
}
def quickSetPool(degrees) {
log.debug "quickSetPool $degrees"
setPoolSetpoint(degrees, 1000)
}
def setPoolSetpoint(degrees, delay = 30000) {
setPoolSetpoint(degrees.toDouble(), delay)
}
def setPoolSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setPoolSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()
], delay)
}
def quickSetSpa(degrees) {
setSpaSetpoint(degrees, 1000)
}
def setSpaSetpoint(degrees, delay = 30000) {
setSpaSetpoint(degrees.toDouble(), delay)
}
def setSpaSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setSpaSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 7, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format()
], delay)
}
//Reports
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
log.debug "configuration: $cmd"
def map = [:]
map.value = cmd.configurationValue
map.displayed = false
switch (cmd.parameterNumber) {
case 1:
map.name = "operationMode"
break;
case 2:
map.name = "firemanTimeout"
break;
case 3:
map.name = "temperatureOffsets"
break;
case 19:
map.name = "poolspaConfig"
break;
default:
return [:]
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd) {
log.debug "Sensor: $cmd"
def map = [:]
map.value = cmd.scaledSensorValue.toString()
map.unit = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv1.ThermostatSetpointReport cmd) {
def map = [:]
map.value = cmd.scaledValue.toString()
map.unit = cmd.scale == 1 ? "F" : "C"
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "poolSetpoint"
break;
case 7:
map.name = "spaSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "$cmd"
if (cmd.value == 0) {
createEvent(name: "switch", value: "off")
} else if (cmd.value == 255) {
createEvent(name: "switch", value: "on")
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceReport cmd) {
log.debug "MultiInstanceReport $cmd"
}
private List loadEndpointInfo() {
if (state.endpointInfo) {
state.endpointInfo
} else if (device.currentValue("epInfo")) {
fromJson(device.currentValue("epInfo"))
} else {
[]
}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd) {
log.debug "$cmd"
updateDataValue("endpoints", cmd.endPoints.toString())
if (!state.endpointInfo) {
state.endpointInfo = loadEndpointInfo()
}
if (state.endpointInfo.size() > cmd.endPoints) {
cmd.endpointInfo
}
state.endpointInfo = [null] * cmd.endPoints
//response(zwave.associationV2.associationGroupingsGet())
[ createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:""),
response(zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: 1)) ]
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) {
log.debug "$cmd"
def result = []
def cmds = []
if(!state.endpointInfo) state.endpointInfo = []
state.endpointInfo[cmd.endPoint - 1] = cmd.format()[6..-1]
if (cmd.endPoint < getDataValue("endpoints").toInteger()) {
cmds = zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: cmd.endPoint + 1).format()
} else {
log.debug "endpointInfo: ${state.endpointInfo.inspect()}"
}
result << createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:"")
if(cmds) result << response(cmds)
result
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationGroupingsReport cmd) {
log.debug "$cmd"
state.groups = cmd.supportedGroupings
if (cmd.supportedGroupings > 1) {
[response(zwave.associationGrpInfoV1.associationGroupInfoGet(groupingIdentifier:2, listMode:1))]
}
}
def zwaveEvent(physicalgraph.zwave.commands.associationgrpinfov1.AssociationGroupInfoReport cmd) {
log.debug "$cmd"
def cmds = []
for (def i = 2; i <= state.groups; i++) {
cmds << response(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:i, nodeId:zwaveHubNodeId))
}
cmds
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
log.debug "$cmd"
def encapsulatedCommand = cmd.encapsulatedCommand([0x32: 3, 0x25: 1, 0x20: 1])
if (encapsulatedCommand) {
if (state.enabledEndpoints.find { it == cmd.sourceEndPoint }) {
def formatCmd = ([cmd.commandClass, cmd.command] + cmd.parameter).collect{ String.format("%02X", it) }.join()
createEvent(name: "epEvent", value: "$cmd.sourceEndPoint:$formatCmd", isStateChange: true, displayed: false, descriptionText: "(fwd to ep $cmd.sourceEndPoint)")
} else {
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Captured zwave command $cmd"
createEvent(descriptionText: "$device.displayName: $cmd", isStateChange: true)
}
//Commands
def epCmd(Integer ep, String cmds) {
log.debug "epCmd: $ep $cmds"
if (cmds.contains('2001FF')){
log.debug "contained 2001FF"
delayBetween([
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF), ep),
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
], 2300)
} else if (cmds.contains('200100')) {
log.debug "contained 2001FF"
delayBetween([
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0), ep),
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
], 2300)
} else if (cmds.contains('2002')) {
encap(zwave.switchBinaryV1.switchBinaryGet(), ep)
} else {
log.warn "No CMD found"
}
}
def enableEpEvents(enabledEndpoints) {
state.enabledEndpoints = enabledEndpoints.split(",").findAll()*.toInteger()
null
}
private command(physicalgraph.zwave.Command cmd) {
log.debug "command: $cmd"
cmd.format()
}
private encap(cmd, endpoint) {
if (endpoint) {
command(zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint, sourceEndPoint: endpoint).encapsulate(cmd))
} else {
command(cmd)
}
}
def poll() {
zwave.sensorMultilevelV1.sensorMultilevelGet().format()
}
def refresh() {
delayBetween([
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:2, destinationEndPoint:2, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:3, destinationEndPoint:3, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:4, destinationEndPoint:4, commandClass:37, command:2).format(),
zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:5, destinationEndPoint:5, commandClass:37, command:2).format(),
zwave.sensorMultilevelV1.sensorMultilevelGet().format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format(),
zwave.configurationV2.configurationGet(parameterNumber: 1).format(),
zwave.configurationV2.configurationGet(parameterNumber: 2).format(),
zwave.configurationV2.configurationGet(parameterNumber: 3).format(),
zwave.configurationV2.configurationGet(parameterNumber: 19).format()
], 3000)
}
def configure() {
def cmds = []
cmds << zwave.configurationV2.configurationSet(configurationValue: [operationMode1.toInteger(), operationMode2.toInteger()], parameterNumber: 1, size: 2).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [tempOffsetwater.toInteger(), tempOffsetair.toInteger(), 0, 0], parameterNumber: 3, size: 4).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [poolSpa1.toInteger()], parameterNumber: 19, size: 1).format()
cmds << zwave.configurationV2.configurationSet(configurationValue: [fireman.toInteger()], parameterNumber: 2, size: 1).format()
delayBetween(cmds, 2500)
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -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)

View File

@@ -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

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -70,7 +70,7 @@ metadata {
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat" state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool" state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto" 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" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
@@ -156,49 +156,47 @@ void poll() {
def generateEvent(Map results) { def generateEvent(Map results) {
log.debug "parsing data $results" log.debug "parsing data $results"
if(results) { if(results) {
results.each { name, value ->
def linkText = getLinkText(device) def linkText = getLinkText(device)
def supportedThermostatModes = [] def isChange = false
def thermostatMode = null def isDisplayed = true
results.each { name, value ->
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText), def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name] handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) { if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() 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") { } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
event << [value: sendValue, unit: temperatureScale, displayed: false] event << [value: sendValue, unit: temperatureScale, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
if (value == true) { isChange = isStateChange(device, name, value.toString())
supportedThermostatModes << ((name == "auxHeatMode") ? "auxheatonly" : name - "Mode") event << [value: value.toString(), isStateChange: isChange, displayed: false]
}
return // as we don't want to send this event here, proceed to next name/value pair
} else if (name=="thermostatFanMode"){ } else if (name=="thermostatFanMode"){
sendEvent(name: "supportedThermostatFanModes", value: fanModes(), displayed: false) isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), data:[supportedThermostatFanModes: fanModes()]] event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="humidity") { } 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") { } else if (name == "deviceAlive") {
isChange = isStateChange(device, name, value.toString())
event['isStateChange'] = isChange
event['displayed'] = false event['displayed'] = false
} else if (name == "thermostatMode") { } else if (name == "thermostatMode") {
thermostatMode = value.toLowerCase() def mode = value.toString()
return // as we don't want to send this event here, proceed to next name/value pair mode = (mode == "auxHeatOnly") ? "emergency heat" : mode
isChange = isStateChange(device, name, mode)
event << [value: mode, isStateChange: isChange, displayed: isDisplayed]
} else { } else {
event << [value: value.toString()] isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
} }
sendEvent(event) 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 () generateSetpointEvent ()
generateStatusEvent () generateStatusEvent ()
} }
@@ -324,7 +322,15 @@ void resumeProgram() {
} }
def modes() { 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() { def fanModes() {
@@ -407,13 +413,11 @@ def setThermostatFanMode(String mode) {
} }
def generateModeEvent(mode) { def generateModeEvent(mode) {
sendEvent(name: "thermostatMode", value: mode, data:[supportedThermostatModes: state.supportedThermostatModes], sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true)
descriptionText: "$device.displayName is in ${mode} mode")
} }
def generateFanModeEvent(fanMode) { def generateFanModeEvent(fanMode) {
sendEvent(name: "thermostatFanMode", value: fanMode, data:[supportedThermostatFanModes: fanModes()], sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
descriptionText: "$device.displayName fan is in ${fanMode} mode")
} }
def generateOperatingStateEvent(operatingState) { def generateOperatingStateEvent(operatingState) {
@@ -449,14 +453,14 @@ def heat() {
} }
def emergencyHeat() { def emergencyHeat() {
auxheatonly() auxHeatOnly()
} }
def auxheatonly() { def auxHeatOnly() {
log.debug "auxheatonly()" log.debug "auxHeatOnly = emergency heat"
def deviceId = device.deviceNetworkId.split(/\./).last() def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auxHeatOnly", deviceId)) if (parent.setMode ("auxHeatOnly", deviceId))
generateModeEvent("auxheatonly") generateModeEvent("emergency heat") // emergency heat = auxHeatOnly
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
@@ -589,7 +593,7 @@ def generateSetpointEvent() {
} else if (mode == "off") { } else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale) sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale)
sendEvent("name":"displayThermostatSetpoint", "value":"Off", displayed: false) 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":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false) sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false)
} }
@@ -628,7 +632,7 @@ void raiseSetpoint() {
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5 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 targetvalue = maxHeatingSetpoint
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) { } else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
targetvalue = maxCoolingSetpoint targetvalue = maxCoolingSetpoint
@@ -674,7 +678,7 @@ void lowerSetpoint() {
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5 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 targetvalue = minHeatingSetpoint
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) { } else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
targetvalue = minCoolingSetpoint targetvalue = minCoolingSetpoint
@@ -715,7 +719,7 @@ void alterSetpoint(temp) {
} }
//step1: check thermostatMode, enforce limits before sending request to cloud //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){ if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value targetCoolingSetpoint = temp.value
@@ -750,7 +754,7 @@ void alterSetpoint(temp) {
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}" log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else { } else {
log.error "Error alterSetpoint()" 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": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") { } else if (mode == "cool") {
@@ -779,7 +783,7 @@ def generateStatusEvent() {
log.debug "Cooling set point = ${coolingSetpoint}" log.debug "Cooling set point = ${coolingSetpoint}"
log.debug "HVAC Mode = ${mode}" log.debug "HVAC Mode = ${mode}"
if (mode == "heat" || mode == "auxheatonly") { if (mode == "heat") {
if (temperature >= heatingSetpoint) { if (temperature >= heatingSetpoint) {
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
} else { } else {
@@ -802,6 +806,8 @@ def generateStatusEvent() {
} }
} else if (mode == "off") { } else if (mode == "off") {
statusText = "Right Now: Off" statusText = "Right Now: Off"
} else if (mode == "emergency heat") { // emergency heat = auxHeatOnly
statusText = "Emergency Heat"
} else { } else {
statusText = "?" statusText = "?"
} }

View File

@@ -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)
} }

View File

@@ -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"])
} }
} }

View File

@@ -23,6 +23,7 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
capability "Light"
capability "Outlet" capability "Outlet"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -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)

View File

@@ -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") {

View File

@@ -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"])
} }
} }

View File

@@ -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}"
} }

View File

@@ -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"] }
}
}