Compare commits

..

1 Commits

Author SHA1 Message Date
murat dalkic
b8e662790d MSA-2022: IPHONE 2017-06-02 15:51:31 -07:00
45 changed files with 407 additions and 450 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

@@ -26,9 +26,7 @@ metadata {
capability "Polling" capability "Polling"
capability "Refresh" capability "Refresh"
capability "Health Check" capability "Health Check"
fingerprint manufacturer: "015D", prod: "0221", model: "251C"
fingerprint manufacturer: "015D", prod: "0221", model: "251C", deviceJoinName: "Show Home 2-Channel Smart Plug"
fingerprint manufacturer: "0312", prod: "B221", model: "251C", deviceJoinName: "Inovelli 2-Channel Smart Plug"
} }
simulator {} simulator {}
preferences {} preferences {}

View File

@@ -1,5 +1,5 @@
/** /**
* Spruce Sensor -updated with SLP model number 5/2017 * Spruce Sensor -Pre-release V2 10/8/2015
* *
* Copyright 2014 Plaid Systems * Copyright 2014 Plaid Systems
* *
@@ -14,33 +14,25 @@
* *
-------10/20/2015 Updates-------- -------10/20/2015 Updates--------
-Fix/add battery reporting interval to update -Fix/add battery reporting interval to update
-remove polling and/or refresh -remove polling and/or refresh(?)
-------5/2017 Updates--------
-Add fingerprints for SLP
-add device health, check every 60mins + 2mins
*/ */
metadata { metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "Plaid Systems") { definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Sensor" capability "Sensor"
capability "Health Check"
//capability "Polling" //capability "Polling"
attribute "maxHum", "string" attribute "maxHum", "string"
attribute "minHum", "string" attribute "minHum", "string"
command "resetHumidity" command "resetHumidity"
command "refresh" command "refresh"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01", deviceJoinName: "Spruce Sensor" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-SLP1", deviceJoinName: "Spruce Sensor"
} }
preferences { preferences {
@@ -301,11 +293,6 @@ def setConfig(){
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized") sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
} }
def installed(){
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
//when device preferences are changed //when device preferences are changed
def updated(){ def updated(){
log.debug "device updated" log.debug "device updated"
@@ -316,8 +303,6 @@ def updated(){
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins") sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
} }
} }
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
} }
//poll //poll
@@ -410,4 +395,4 @@ private byte[] reverseArray(byte[] array) {
i++; i++;
} }
return array return array
} }

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

@@ -27,9 +27,13 @@ Works with:
## Device Health ## Device Health
Aeon Labs MultiSensor 6 is polled by the hub. Aeon Labs MultiSensor 6 is polled by the hub.
Aeon MultiSensor reports in once every hour. As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __122min__ checkInterval * __32min__ checkInterval
## Troubleshooting ## Troubleshooting

View File

@@ -130,13 +130,13 @@ metadata {
} }
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 122min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def updated() { def updated() {
// Device-Watch simply pings if no device events received for 122min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}" log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"

View File

@@ -22,6 +22,8 @@ metadata {
capability "Battery" capability "Battery"
capability "Health Check" capability "Health Check"
command "configureAfterSecure"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
fingerprint mfr:"0086", prod:"0102", model:"004A", deviceJoinName: "Aeon Labs MultiSensor (Gen 5)" fingerprint mfr:"0086", prod:"0102", model:"004A", deviceJoinName: "Aeon Labs MultiSensor (Gen 5)"
} }
@@ -89,12 +91,12 @@ metadata {
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:"" state "battery", label:'${currentValue}% battery', unit:""
} }
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("configureAfterSecure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configure", icon:"st.secondary.configure" state "configure", label:'', action:"configureAfterSecure", icon:"st.secondary.configure"
} }
main(["motion", "temperature", "humidity", "illuminance"]) main(["motion", "temperature", "humidity", "illuminance"])
details(["motion", "temperature", "humidity", "illuminance", "battery", "configure"]) details(["motion", "temperature", "humidity", "illuminance", "battery", "configureAfterSecure"])
} }
} }
@@ -129,8 +131,8 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
if (!isConfigured()) { if (!isConfigured()) {
// we're still in the process of configuring a newly joined device // we're still in the process of configuring a newly joined device
log.debug("not sending wakeUpNoMoreInformation yet: late configure") log.debug("not sending wakeUpNoMoreInformation yet")
result += response(configure()) result += response(configureAfterSecure())
} else { } else {
result += response(zwave.wakeUpV1.wakeUpNoMoreInformation()) result += response(zwave.wakeUpV1.wakeUpNoMoreInformation())
} }
@@ -148,6 +150,11 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat
} }
} }
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
// log.debug "Received SecurityCommandsSupportedReport"
response(configureAfterSecure())
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ] def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) { if (cmd.batteryLevel == 0xFF) {
@@ -219,6 +226,36 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: cmd.toString(), isStateChange: false) createEvent(descriptionText: cmd.toString(), isStateChange: false)
} }
def configureAfterSecure() {
// log.debug "configureAfterSecure()"
def request = [
// send temperature, humidity, and illuminance every 8 minutes
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 128|64|32),
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60),
// send battery every 20 hours
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1),
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 20*60*60),
// send no-motion report 60 seconds after motion stops
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 60),
// send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2),
// disable notification-style motion events
zwave.notificationV3.notificationSet(notificationType: 7, notificationStatus: 0),
zwave.batteryV1.batteryGet(),
zwave.sensorBinaryV2.sensorBinaryGet(sensorType:0x0C)
]
setConfigured()
secureSequence(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
}
/** /**
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
@@ -228,36 +265,7 @@ def ping() {
def configure() { def configure() {
// log.debug "configure()" // log.debug "configure()"
def request = [] //["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
// send temperature, humidity, and illuminance every 8 minutes
request << zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 128|64|32)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60)
// send battery every 20 hours
request << zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1)
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 20*60*60)
// send no-motion report 60 seconds after motion stops
request << zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 60)
// send binary sensor report instead of basic set for motion
request << zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2)
// Turn on the Multisensor Gen5 PIR sensor
request << zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: 1)
// disable notification-style motion events
request << zwave.notificationV3.notificationSet(notificationType: 7, notificationStatus: 0)
request << zwave.batteryV1.batteryGet()
request << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C) //motion
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01) //temperature
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x03) //illuminance
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x05) //humidity
setConfigured()
secureSequence(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
} }
private setConfigured() { private setConfigured() {

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Bose SoundTouch * Bose SoundTouch
* *

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 = ["off"] 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,16 +48,14 @@ 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"])
} }
} }
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
response(refresh())
} }
def updated(){ def updated(){
@@ -87,17 +85,11 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
def open() { def open() {
delayBetween([ zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format()
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
], 500)
} }
def close() { def close() {
delayBetween([ zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format()
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
], 500)
} }
/** /**
@@ -113,6 +105,6 @@ def refresh() {
def createEventWithDebug(eventMap) { def createEventWithDebug(eventMap) {
def event = createEvent(eventMap) def event = createEvent(eventMap)
log.debug "Event created with ${event?.name}:${event?.value} - ${event?.descriptionText}" log.debug "Event created with ${event?.descriptionText}"
return event return event
} }

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue Bloom * Hue Bloom
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue Bridge * Hue Bridge
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue Bulb * Hue Bulb
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue Lux Bulb * Hue Lux Bulb
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue White Ambiance Bulb * Hue White Ambiance Bulb
* *

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

@@ -105,8 +105,6 @@ def parse(String description) {
} else { } else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
} }
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} }
} }
} else if (map.name == "temperature") { } else if (map.name == "temperature") {
@@ -131,10 +129,6 @@ def parse(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
} }
@@ -203,8 +197,7 @@ def ping() {
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
return refreshCmds + zigbee.enrollResponse() return refreshCmds + zigbee.enrollResponse()
} }

View File

@@ -111,8 +111,6 @@ def parse(String description) {
def value = descMap.value.endsWith("01") ? "active" : "inactive" def value = descMap.value.endsWith("01") ? "active" : "inactive"
log.debug "Doing a read attr motion event" log.debug "Doing a read attr motion event"
map = getMotionResult(value) map = getMotionResult(value)
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} }
} }
} else if (map.name == "temperature") { } else if (map.name == "temperature") {
@@ -137,10 +135,6 @@ def parse(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion // Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
} }
@@ -171,8 +165,8 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
result.value = pct result.value = pct
} else { } else {
def minVolts = 2.4 def minVolts = 2.1
def maxVolts = 2.7 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0) if (roundedPct <= 0)
@@ -206,8 +200,7 @@ def refresh() {
log.debug "refresh called" log.debug "refresh called"
def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000)
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
return refreshCmds + zigbee.enrollResponse() return refreshCmds + zigbee.enrollResponse()
} }

View File

@@ -134,9 +134,8 @@ def parse(String description) {
} else { } else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
} }
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
maps += translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} else { } else {
maps += handleAcceleration(descMap) maps += handleAcceleration(descMap)
} }
} }
@@ -230,11 +229,6 @@ private List<Map> parseAxis(List<Map> attrData) {
private List<Map> parseIasMessage(String description) { private List<Map> parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private List<Map> translateZoneStatus(ZoneStatus zs) {
List<Map> results = [] List<Map> results = []
if (garageSensor != "Yes") { if (garageSensor != "Yes") {
@@ -274,7 +268,7 @@ private Map getBatteryResult(rawValue) {
result.value = pct result.value = pct
} else { } else {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 2.7 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0) if (roundedPct <= 0)
@@ -319,7 +313,7 @@ def refresh() {
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) + zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS) + zigbee.enrollResponse() zigbee.enrollResponse()
return refreshCmds return refreshCmds
} }

View File

@@ -49,6 +49,6 @@ def arrived() {
def departed() { def departed() {
log.trace "Executing 'departed'" log.trace "Executing 'arrived'"
sendEvent(name: "presence", value: "not present") sendEvent(name: "presence", value: "not present")
} }

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *

View File

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

View File

@@ -1,42 +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
ZigBee Button 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 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

@@ -13,8 +13,6 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType import physicalgraph.zigbee.zcl.DataType
metadata { metadata {
@@ -26,7 +24,6 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check"
command "enrollResponse" command "enrollResponse"
@@ -252,8 +249,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)
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) { if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2) sendEvent(name: "numberOfButtons", value: 2)
} }

View File

@@ -66,29 +66,22 @@ def parse(String description) {
else { else {
sendEvent(event) sendEvent(event)
} }
} else { }
def descMap = zigbee.parseDescriptionAsMap(description) else {
if (descMap && descMap.clusterInt == 0x0006 && descMap.commandInt == 0x07) { def cluster = zigbee.parse(description)
if (descMap.data[0] == "00") {
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
} else { }
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}" log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
} }
} else if (device.getDataValue("manufacturer") == "sengled" && descMap && descMap.clusterInt == 0x0008 && descMap.attrInt == 0x0000) { }
// This is being done because the sengled element touch/classic incorrectly uses the value 0xFF for the max level. else {
// Per the ZCL spec for the UINT8 data type 0xFF is an invalid value, and 0xFE should be the max. Here we
// manually handle the invalid attribute value since it will be ignored by getEvent as an invalid value.
// We also set the level of the bulb to 0xFE so future level reports will be 0xFE until it is changed by
// something else.
if (descMap.value.toUpperCase() == "FF") {
descMap.value = "FE"
}
sendHubCommand(zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, "FE0000").collect { new physicalgraph.device.HubAction(it) }, 0)
sendEvent(zigbee.getEventFromAttrData(descMap.clusterInt, descMap.attrInt, descMap.encoding, descMap.value))
} else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${descMap}" log.debug "${cluster}"
} }
} }
} }

View File

@@ -103,12 +103,12 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
state.hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100) def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
runIn(5, updateColor, [overwrite: true]) sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
} }
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
state.saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100) def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
runIn(5, updateColor, [overwrite: true]) sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
} }
} }
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) { else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
@@ -127,11 +127,6 @@ def parse(String description) {
} }
} }
def updateColor() {
sendEvent(name: "hue", value: state.hueValue, descriptionText: "Color has changed")
sendEvent(name: "saturation", value: state.saturationValue, descriptionText: "Color has changed", displayed: false)
}
def on() { def on() {
zigbee.on() zigbee.on()
} }
@@ -209,9 +204,9 @@ def setColor(value){
log.trace "setColor($value)" log.trace "setColor($value)"
zigbee.on() + zigbee.on() +
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND, zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") + getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
} }
def setHue(value) { def setHue(value) {

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

@@ -15,9 +15,10 @@ metadata {
definition (name: "Z-Wave Thermostat", namespace: "smartthings", author: "SmartThings") { definition (name: "Z-Wave Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Thermostat" capability "Thermostat"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Polling"
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
@@ -116,7 +117,7 @@ metadata {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff" state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
} }
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"polling.poll", icon:"st.secondary.refresh"
} }
main "temperature" main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"]) details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"])
@@ -124,20 +125,13 @@ metadata {
} }
def installed(){ def installed(){
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())) // Device-Watch simply pings if no device events received for 32min(checkInterval)
initialize() sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def updated(){ def updated(){
initialize()
}
def initialize() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
unschedule()
runEvery5Minutes("refresh")
refresh()
} }
def parse(String description) def parse(String description)
@@ -155,7 +149,6 @@ def parse(String description)
] ]
if (map.name == "thermostatMode") { if (map.name == "thermostatMode") {
state.lastTriedMode = map.value state.lastTriedMode = map.value
map.data = [supportedThermostatModes:state.supportedThermostatModes]
if (map.value == "cool") { if (map.value == "cool") {
map2.value = device.latestValue("coolingSetpoint") map2.value = device.latestValue("coolingSetpoint")
log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}" log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}"
@@ -179,7 +172,6 @@ def parse(String description)
} }
} else if (map.name == "thermostatFanMode" && map.isStateChange) { } else if (map.name == "thermostatFanMode" && map.isStateChange) {
state.lastTriedFanMode = map.value state.lastTriedFanMode = map.value
map.data = [supportedThermostatFanModes: state.supportedThermostatFanModes]
} }
log.debug "Parse returned $result" log.debug "Parse returned $result"
result result
@@ -313,26 +305,26 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanMod
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
def supportedModes = [] def supportedModes = ""
if(cmd.off) { supportedModes << "off" } if(cmd.off) { supportedModes += "off " }
if(cmd.heat) { supportedModes << "heat" } if(cmd.heat) { supportedModes += "heat " }
if(cmd.cool) { supportedModes << "cool" } if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergency heat " }
if(cmd.auto) { supportedModes << "auto" } if(cmd.cool) { supportedModes += "cool " }
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" } if(cmd.auto) { supportedModes += "auto " }
state.supportedThermostatModes = supportedModes state.supportedModes = supportedModes
sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false) // No events to be generated, return empty map
return [:] return [:]
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = [] def supportedFanModes = ""
if(cmd.auto) { supportedFanModes << "auto" } // "fanAuto " if(cmd.auto) { supportedFanModes += "auto " } // "fanAuto "
if(cmd.circulation) { supportedFanModes << "circulate" } // "fanCirculate" if(cmd.low) { supportedFanModes += "on " } // "fanOn"
if(cmd.low) { supportedFanModes << "on" } // "fanOn" if(cmd.circulation) { supportedFanModes += "circulate " } // "fanCirculate"
state.supportedThermostatFanModes = supportedFanModes state.supportedFanModes = supportedFanModes
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false) // No events to be generated, return empty map
return [:] return [:]
} }
@@ -345,17 +337,15 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
// Command Implementations // Command Implementations
def refresh() { def poll() {
def cmds = [] delayBetween([
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format()) zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format()) zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format()) zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(),
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format()) zwave.thermostatModeV2.thermostatModeGet().format(),
cmds << new physicalgraph.device.HubAction(zwave.sensorMultilevelV2.sensorMultilevelGet().format()) // current temperature zwave.thermostatFanModeV3.thermostatFanModeGet().format(),
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()) zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) ], 2300)
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(cmds)
} }
def quickSetHeat(degrees) { def quickSetHeat(degrees) {
@@ -426,14 +416,28 @@ def ping() {
poll() poll()
} }
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format(),
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()
], 2300)
}
def modes() { def modes() {
return state.supportedThermostatModes ["off", "heat", "cool", "auto", "emergency heat"]
} }
def switchMode() { def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: ["off"] def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedThermostatModes") def supportedModes = getDataByName("supportedModes")
def modeOrder = modes() def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
@@ -450,7 +454,7 @@ def switchMode() {
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedThermostatModes") def supportedModes = getDataByName("supportedModes")
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if (nextMode in modes()) { if (nextMode in modes()) {
state.lastTriedMode = nextMode state.lastTriedMode = nextMode
@@ -462,9 +466,9 @@ def switchToMode(nextMode) {
def switchFanMode() { def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: ["off"] def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedThermostatFanModes") ?: ["auto", "on"] def supportedModes = getDataByName("supportedFanModes") ?: "auto on" // "fanAuto fanOn"
def modeOrder = state.supportedThermostatFanModes def modeOrder = ["auto", "circulate", "on"] // "fanAuto", "fanCirculate", "fanOn"
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto" while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto"
@@ -474,7 +478,7 @@ def switchFanMode() {
} }
def switchToFanMode(nextMode) { def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedThermostatFanModes") def supportedFanModes = getDataByName("supportedFanModes")
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
def returnCommand def returnCommand

View File

@@ -58,7 +58,6 @@ metadata {
def installed() { def installed() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
response(refresh())
} }
def updated() { def updated() {

View File

@@ -0,0 +1,145 @@
/**
* JSON
*
* Copyright 2015 Jesse Newland
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "JSON API",
namespace: "jnewland",
author: "Jesse Newland",
description: "A JSON API for SmartThings",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: true)
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
if (!state.accessToken) {
createAccessToken()
}
}
preferences {
page(name: "copyConfig")
}
def copyConfig() {
if (!state.accessToken) {
createAccessToken()
}
dynamicPage(name: "copyConfig", title: "Config", install:true) {
section("Select devices to include in the /devices API call") {
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
input "hues", "capability.colorControl", title: "Hues", multiple: true, required: false
}
section() {
paragraph "View this SmartApp's configuration to use it in other places."
href url:"https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/config?access_token=${state.accessToken}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
}
section() {
href url:"https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/devices?access_token=${state.accessToken}", style:"embedded", required:false, title:"Debug", description:"View accessories JSON"
}
}
}
def renderConfig() {
def configJson = new groovy.json.JsonOutput().toJson([
description: "JSON API",
platforms: [
[
platform: "SmartThings",
name: "SmartThings",
app_id: app.id,
access_token: state.accessToken
]
],
])
def configString = new groovy.json.JsonOutput().prettyPrint(configJson)
render contentType: "text/plain", data: configString
}
def deviceCommandMap(device, type) {
device.supportedCommands.collectEntries { command->
def commandUrl = "https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/${type}/${device.id}/command/${command.name}?access_token=${state.accessToken}"
[
(command.name): commandUrl
]
}
}
def authorizedDevices() {
[
switches: switches,
hues: hues
]
}
def renderDevices() {
def deviceData = authorizedDevices().collectEntries { devices->
[
(devices.key): devices.value.collect { device->
[
name: device.displayName,
commands: deviceCommandMap(device, devices.key)
]
}
]
}
def deviceJson = new groovy.json.JsonOutput().toJson(deviceData)
def deviceString = new groovy.json.JsonOutput().prettyPrint(deviceJson)
render contentType: "application/json", data: deviceString
}
def deviceCommand() {
def device = authorizedDevices()[params.type].find { it.id == params.id }
def command = params.command
if (!device) {
httpError(404, "Device not found")
} else {
if (params.value) {
device."$command"(params.value)
} else {
device."$command"()
}
}
}
mappings {
if (!params.access_token || (params.access_token && params.access_token != state.accessToken)) {
path("/devices") { action: [GET: "authError"] }
path("/config") { action: [GET: "authError"] }
path("/:type/:id/command/:command") { action: [PUT: "authError"] }
} else {
path("/devices") { action: [GET: "renderDevices"] }
path("/config") { action: [GET: "renderConfig"] }
path("/:type/:id/command/:command") { action: [PUT: "deviceCommand"] }
}
}
def authError() {
[error: "Permission denied"]
}

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

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Bose SoundTouch (Connect) * Bose SoundTouch (Connect)
* *

View File

@@ -27,9 +27,10 @@ definition(
preferences { preferences {
page(name: "selectButton") page(name: "selectButton")
for (def i=1; i<=8; i++) { page(name: "configureButton1")
page(name: "configureButton$i") page(name: "configureButton2")
} page(name: "configureButton3")
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
@@ -59,45 +60,22 @@ def selectButton() {
} }
} }
def createPage(pageNum) {
if ((state.numButton == pageNum) || (pageNum == 8))
state.installCondition = true
dynamicPage(name: "configureButton$pageNum", title: "Set up button $pageNum here",
nextPage: "configureButton${pageNum+1}", install: state.installCondition, uninstall: configured(), getButtonSections(pageNum))
}
def configureButton1() { def configureButton1() {
state.numButton = buttonDevice.currentState("numberOfButtons")?.longValue ?: 4 dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
log.debug "state variable numButton: ${state.numButton}" nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
state.installCondition = false
createPage(1)
} }
def configureButton2() { def configureButton2() {
createPage(2) dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
} }
def configureButton3() { def configureButton3() {
createPage(3) dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
} }
def configureButton4() { def configureButton4() {
createPage(4) dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
} install: true, uninstall: true, getButtonSections(4))
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
} }
def getButtonSections(buttonNumber) { def getButtonSections(buttonNumber) {

View File

@@ -202,8 +202,7 @@ def inputSelectionPage() {
section("options variations") { section("options variations") {
paragraph "tap these elements and look at the differences when selecting an option" paragraph "tap these elements and look at the differences when selecting an option"
input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", options: ["Thing 1", "Thing 2", "(Complicated) Thing 3"]) input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionSimpleGrouped", title: "Simple (Grouped) options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions) input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions)
} }
@@ -215,15 +214,15 @@ def inputSelectionPage() {
section("segmented") { section("segmented") {
paragraph "segmented should only work if there are either 2 or 3 options to choose from" paragraph "segmented should only work if there are either 2 or 3 options to choose from"
input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", options: ["One"]) input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"]))
input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", options: ["One", "Two", "Three", "Four"]) input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"]))
paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected" paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected"
input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"]) input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"])
input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"]) input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"])
paragraph "specifying defaultValue still works with segmented selection elements" paragraph "specifying defaultValue still works with segmented selection elements"
input(type: "enum", name: "selectionSegmentedWithDefault", style: "segmented", title: "defaulted to 'two'", options: ["One", "Two", "Three"], defaultValue: "Two") input(type: "enum", name: "selectionSegmentedWithDefault", title: "defaulted to 'two'", groupedOptions: addGroup(["One", "Two", "Three"]), defaultValue: "Two")
} }
section("required: true") { section("required: true") {
@@ -232,8 +231,6 @@ def inputSelectionPage() {
section("multiple: true") { section("multiple: true") {
input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true) input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true)
input(type: "enum", name: "selectionMultipleDefault1", title: "This allows multiple selections with a single default", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: "an option")
input(type: "enum", name: "selectionMultipleDefault2", title: "This allows multiple selections with multiple defaults", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: ["an option", "another option"])
} }
section("with image") { section("with image") {

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Hue Service Manager * Hue Service Manager
* *
@@ -1085,6 +1083,7 @@ def on(childDevice) {
log.debug "Executing 'on'" log.debug "Executing 'on'"
def id = getId(childDevice) def id = getId(childDevice)
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true]) put("lights/$id/state", [on: true])
return "Bulb is turning On" return "Bulb is turning On"
} }
@@ -1093,6 +1092,7 @@ def off(childDevice) {
log.debug "Executing 'off'" log.debug "Executing 'off'"
def id = getId(childDevice) def id = getId(childDevice)
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false]) put("lights/$id/state", [on: false])
return "Bulb is turning Off" return "Bulb is turning Off"
} }
@@ -1108,6 +1108,8 @@ def setLevel(childDevice, percent) {
else else
level = Math.min(Math.round(percent * 254 / 100), 254) level = Math.min(Math.round(percent * 254 / 100), 254)
createSwitchEvent(childDevice, level > 0, percent)
// For Zigbee lights, if level is set to 0 ST just turns them off without changing level // For Zigbee lights, if level is set to 0 ST just turns them off without changing level
// that means that the light will still be on when on is called next time // that means that the light will still be on when on is called next time
// Lets emulate that here // Lets emulate that here
@@ -1126,6 +1128,7 @@ def setSaturation(childDevice, percent) {
// 0 - 254 // 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254) def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [sat: level, on: true]) put("lights/$id/state", [sat: level, on: true])
return "Setting saturation to $percent" return "Setting saturation to $percent"
} }
@@ -1137,6 +1140,7 @@ def setHue(childDevice, percent) {
// 0 - 65535 // 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535) def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on? // TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [hue: level, on: true]) put("lights/$id/state", [hue: level, on: true])
return "Setting hue to $percent" return "Setting hue to $percent"
} }
@@ -1147,6 +1151,7 @@ def setColorTemperature(childDevice, huesettings) {
updateInProgress() updateInProgress()
// 153 (6500K) to 500 (2000K) // 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000 / huesettings) def ct = hueSettings == 6500 ? 153 : Math.round(1000000 / huesettings)
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [ct: ct, on: true]) put("lights/$id/state", [ct: ct, on: true])
return "Setting color temperature to $ct" return "Setting color temperature to $ct"
} }
@@ -1205,6 +1210,7 @@ def setColor(childDevice, huesettings) {
if (huesettings.switch == "off") if (huesettings.switch == "off")
value.on = false value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value) put("lights/$id/state", value)
return "Setting color to $value" return "Setting color to $value"
} }
@@ -1316,6 +1322,32 @@ private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it } return location.hubs*.firmwareVersionString.findAll { it }
} }
/**
* Sends appropriate turningOn/turningOff state events depending on switch or level changes.
*
* @param childDevice device to send event for
* @param setSwitch The new switch state, "on" or "off"
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
* that should generate "off" instead of level change
*/
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
if (setLevel == null) {
setLevel = childDevice.device?.currentValue("level")
}
// Create on, off, turningOn or turningOff event as necessary
def currentState = childDevice.device?.currentValue("switch")
if ((currentState == "off" || currentState == "turningOff")) {
if (setSwitch == "on" || setLevel > 0) {
childDevice.sendEvent(name: "switch", value: "turningOn", displayed: false)
}
} else if ((currentState == "on" || currentState == "turningOn")) {
if (setSwitch == "off" || setLevel == 0) {
childDevice.sendEvent(name: "switch", value: "turningOff", displayed: false)
}
}
}
/** /**
* Return the supported color range for different Hue lights. If model is not specified * Return the supported color range for different Hue lights. If model is not specified
* it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices * it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices

View File

@@ -72,7 +72,7 @@ def authPage() {
log.debug "have LIFX access token" log.debug "have LIFX access token"
def options = locationOptions() ?: [] def options = locationOptions() ?: []
def count = options.size().toString() def count = options.size()
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) { return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
section("Select your location") { section("Select your location") {

View File

@@ -1,5 +1,3 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *