Compare commits

..

1 Commits

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

View File

@@ -9,7 +9,7 @@ apply plugin: 'smartthings-slack'
buildscript {
dependencies {
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.12"
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.11"
}
repositories {
mavenLocal()
@@ -19,7 +19,7 @@ buildscript {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
url "https://artifactory.smartthings.com/libs-release-local"
}
}
}
@@ -32,7 +32,7 @@ repositories {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
url "https://artifactory.smartthings.com/libs-release-local"
}
}
@@ -51,10 +51,10 @@ sourceSets {
dependencies {
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
devicetypesCompile 'smartthings:appengine-z-wave:0.1.3'
devicetypesCompile 'smartthings:appengine-zigbee:0.1.12'
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
smartappsCompile 'smartthings:appengine-common:0.1.9'
smartappsCompile 'smartthings:appengine-common:0.1.8'
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
smartappsCompile 'org.grails:grails-web:2.3.11'
smartappsCompile 'org.json:json:20140107'
@@ -74,19 +74,19 @@ slackSendMessage {
String username
switch (branch) {
case 'master':
username = 'DEV'
username = 'Hickory'
iconUrl = wolverine
color = '#35D0F2'
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
break
case 'staging':
username = 'STG'
username = 'Dickory'
iconUrl = beach
color = '#FFDE20'
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
break
case 'production':
username = 'PRD'
username = 'Dock'
iconUrl = drinks
color = '#FF1D23'
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'

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
*
@@ -14,33 +14,25 @@
*
-------10/20/2015 Updates--------
-Fix/add battery reporting interval to update
-remove polling and/or refresh
-------5/2017 Updates--------
-Add fingerprints for SLP
-add device health, check every 60mins + 2mins
-remove polling and/or refresh(?)
*/
metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "Plaid Systems") {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
capability "Configuration"
capability "Battery"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"
capability "Sensor"
capability "Health Check"
//capability "Polling"
attribute "maxHum", "string"
attribute "minHum", "string"
command "resetHumidity"
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-SLP1", deviceJoinName: "Spruce Sensor"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
}
preferences {
@@ -301,11 +293,6 @@ def setConfig(){
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
def 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")
}
}
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
//poll
@@ -410,4 +395,4 @@ private byte[] reverseArray(byte[] array) {
i++;
}
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
*
@@ -20,7 +19,6 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
@@ -133,9 +131,6 @@ def updated() {
}
def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
def zwMap = getZwaveInfo()
def buttons = 4 // Default for Key Fob

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
*
@@ -19,7 +18,6 @@ metadata {
capability "Holdable Button"
capability "Configuration"
capability "Sensor"
capability "Health Check"
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B"
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons
@@ -121,7 +119,5 @@ def updated() {
}
def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -27,9 +27,13 @@ Works with:
## Device Health
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

View File

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

View File

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

View File

@@ -300,21 +300,15 @@ def setColor(value) {
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
}
if(value.hue) {
sendEvent(name: "hue", value: value.hue, displayed: false)
}
if(value.saturation) {
sendEvent(name: "saturation", value: value.saturation, displayed: false)
}
if(value.hex?.trim()) {
sendEvent(name: "color", value: value.hex, displayed: false)
}
if (value.level) {
sendEvent(name: "level", value: value.level)
}
if (value.switch?.trim()) {
sendEvent(name: "switch", value: value.switch)
}
sendEvent(name: "hue", value: value.hue, displayed: false)
sendEvent(name: "saturation", value: value.saturation, displayed: false)
sendEvent(name: "color", value: value.hex, displayed: false)
if (value.level) {
sendEvent(name: "level", value: value.level)
}
if (value.switch) {
sendEvent(name: "switch", value: value.switch)
}
sendRGB(value.rh, value.gh, value.bh)
}

View File

@@ -35,8 +35,8 @@ metadata {
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
@@ -48,8 +48,8 @@ metadata {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "contact"
details(["contact","refresh"])
main "valve"
details(["valve","refresh"])
}
}

View File

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

View File

@@ -105,8 +105,6 @@ def parse(String description) {
} else {
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") {
@@ -131,10 +129,6 @@ def parse(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
}
@@ -203,8 +197,7 @@ def ping() {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
return refreshCmds + zigbee.enrollResponse()
}

View File

@@ -111,8 +111,6 @@ def parse(String description) {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
log.debug "Doing a read attr motion event"
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") {
@@ -137,10 +135,6 @@ def parse(String description) {
private Map parseIasMessage(String 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
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
}
@@ -171,8 +165,8 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.4
def maxVolts = 2.7
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
@@ -206,8 +200,7 @@ def refresh() {
log.debug "refresh called"
def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000)
return refreshCmds + zigbee.enrollResponse()
}

View File

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

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.
*
*/
import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType
metadata {
@@ -26,7 +24,6 @@ metadata {
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "enrollResponse"
@@ -252,8 +249,6 @@ def updated() {
}
def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2)
}

View File

@@ -38,8 +38,8 @@ metadata {
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
@@ -58,8 +58,8 @@ metadata {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["contact"])
details(["contact", "battery", "refresh"])
main(["valve"])
details(["valve", "battery", "refresh"])
}
}

View File

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

View File

@@ -58,7 +58,6 @@ metadata {
def installed() {
// 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])
response(refresh())
}
def updated() {

View File

@@ -1,131 +0,0 @@
/**
* FortrezZ MIMO2+ B-Side
*
* Copyright 2016 FortrezZ, LLC
*
* 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: "FortrezZ MIMO2+ B-Side",
namespace: "fortrezz",
author: "FortrezZ, LLC",
description: "Breaks the MIMO2 into two separate devices to allow automation on SIG2 and Relay 2.",
category: "Convenience",
iconUrl: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square-200-1.png",
iconX2Url: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square-500.png",
iconX3Url: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square.png",
singleInstance: true)
preferences {
section("Title") {
input(name: "devices", type: "capability.voltageMeasurement", title: "MIMO2 devices", description: null, required: true, submitOnChange: true, multiple: true)
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize(){
log.debug("Devices: ${settings.devices}")
settings.devices.each {//deviceId ->
subscribe(it, "powered", events)
subscribe(it, "switch2", events)
subscribe(it, "contact2", events)
subscribe(it, "voltage2", events)
subscribe(it, "relay2", events)
subscribe(it, "anaDig2", events)
try {
def existingDevice = getChildDevice(it.id)
if(!existingDevice) {
log.debug("Device ID: ${existingDevice}")
def childDevice = addChildDevice("fortrezz", "FortrezZ MIMO2+ B-Side", it.id, null, [name: "Device.${it.id}", label: "${it.name} B-Side", completedSetup: true])
}
} catch (e) {
log.error "Error creating device: ${e}"
}
}
getChildDevices().each {
def test = it
def search = settings.devices.find { getChildDevice(it.id).id == test.id }
if(!search) {
removeChildDevices(test)
}
}
}
def uninstalled() {
removeChildDevices(getChildDevices())
}
private removeChildDevices(delete) {
delete.each {
deleteChildDevice(it.deviceNetworkId)
}
}
def on2(child) {
log.debug("on2")
def ret = child
settings.devices.each {//deviceId ->
def ch = getChildDevice(it.id)
if(child == ch.id) {
ret = "${child}, ${it.id}"
it.on2()
}
}
return ret
}
def off2(child) {
log.debug("off2")
def ret = child
settings.devices.each {//deviceId ->
def ch = getChildDevice(it.id)
if(child == ch.id) {
ret = "${child}, ${it.id}"
it.off2()
}
}
return ret
}
def refresh2(child) {
log.debug("refresh2")
def ret = child
settings.devices.each {//deviceId ->
def ch = getChildDevice(it.id)
if(child == ch.id) {
ret = "${child}, ${it.id}"
it.refresh()
}
}
return ret
}
def events(evt) {
def ch = getChildDevice(evt.device.id)
ch.eventParse(evt);
log.debug("${evt.device.id} triggered ${evt.name}")
}
// TODO: implement event handlers

View File

@@ -195,10 +195,7 @@ def registerDeviceChange() {
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
// state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
// For now, we will only have one subscription endpoint per device
state.deviceSubscriptionMap.remove(deviceId)
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
}

View File

@@ -27,9 +27,10 @@ definition(
preferences {
page(name: "selectButton")
for (def i=1; i<=8; i++) {
page(name: "configureButton$i")
}
page(name: "configureButton1")
page(name: "configureButton2")
page(name: "configureButton3")
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") {
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() {
state.numButton = buttonDevice.currentState("numberOfButtons")?.longValue ?: 4
log.debug "state variable numButton: ${state.numButton}"
state.installCondition = false
createPage(1)
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
}
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() {
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() {
createPage(4)
}
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
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 getButtonSections(buttonNumber) {

View File

@@ -202,8 +202,7 @@ def inputSelectionPage() {
section("options variations") {
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: "selectionSimpleGrouped", title: "Simple (Grouped) options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionSimple", title: "Simple 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)
}
@@ -215,15 +214,15 @@ def inputSelectionPage() {
section("segmented") {
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: "selectionSegmented4", style: "segmented", title: "4 options", options: ["One", "Two", "Three", "Four"])
input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"]))
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"
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"])
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") {
@@ -232,8 +231,6 @@ def inputSelectionPage() {
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: "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") {

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

View File

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