Compare commits

..

1 Commits

Author SHA1 Message Date
Michael Macaulay
d787c1b41e MSA-1338: I believe this should replace the current app on the app store "TCP Bulbs (connect)" as it's a more reliable integration.
I did a couple of things to make this more reliable:
1.  I re-fetch the authorization token every 5 minutes
2.  If a request fails, I again re-try to fetch the token and re-submit the request
3.  I try again on 400 errors in addition to 500 and 401 errors.

Code is all open source under Apache 2.0 license hosted at github at https://github.com/mmacaula/tcp-bulbs
2016-06-04 15:02:55 -05:00
46 changed files with 1575 additions and 1956 deletions

View File

@@ -1,37 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Beacon Capability", namespace: "capabilities", author: "SmartThings") {
capability "Beacon"
}
simulator {
status "present": "beacon: present"
status "not present": "beacon: not present"
}
tiles {
standardTile("beacon", "device.beacon", width: 2, height: 2) {
state("not present", label:'not present', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
state("present", label:'present', icon:"st.presence.tile.present", backgroundColor:"#53a7c0")
}
main "beacon"
details "beacon"
}
}
def parse(String description) {
def pair = description.split(":")
createEvent(name: pair[0].trim(), value: pair[1].trim())
}

View File

@@ -22,14 +22,12 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Health Check"
attribute "tamper", "enum", ["detected", "clear"] attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string" attribute "batteryStatus", "string"
attribute "powerSupply", "enum", ["USB Cable", "Battery"] attribute "powerSupply", "enum", ["USB Cable", "Battery"]
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A" fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
} }
simulator { simulator {
@@ -328,9 +326,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
def configure() { def configure() {
// allow device user configured or default 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
// This sensor joins as a secure device if you double-click the button to include it // This sensor joins as a secure device if you double-click the button to include it
log.debug "${device.displayName} is configuring its settings" log.debug "${device.displayName} is configuring its settings"
def request = [] def request = []
@@ -357,7 +352,7 @@ def configure() {
motionSensitivity == "minimum" ? 0 : 64) motionSensitivity == "minimum" ? 0 : 64)
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins) //5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: (8*60)) //association group 1 request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2 request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2

View File

@@ -20,9 +20,6 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
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"
} }
@@ -248,8 +245,6 @@ def configureAfterSecure() {
def configure() { def configure() {
// log.debug "configure()" // log.debug "configure()"
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet()) //["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
// allow device 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
} }
private setConfigured() { private setConfigured() {

View File

@@ -20,7 +20,6 @@ metadata {
capability "Illuminance Measurement" capability "Illuminance Measurement"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Health Check"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86" fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
} }
@@ -181,9 +180,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
def configure() { def configure() {
// allow device 10 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
delayBetween([ delayBetween([
// send binary sensor report instead of basic set for motion // send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(), zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),

View File

@@ -64,10 +64,8 @@ metadata {
} }
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff" state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff"
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff" state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
} }
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) { valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
state "station1", label:'${currentValue}', action:"preset1" state "station1", label:'${currentValue}', action:"preset1"
@@ -749,16 +747,8 @@ def cb_boseSetInput(xml, input) {
*/ */
def boseSetPowerState(boolean enable) { def boseSetPowerState(boolean enable) {
log.info "boseSetPowerState(${enable})" log.info "boseSetPowerState(${enable})"
// Fix to get faster update of power status back from speaker after sending on/off queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
// Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand return boseRefreshNowPlaying()
// Note: This is a temporary hack that should be replaced by a re-design of the
// DTH to use sendHubCommand for all commands
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(boseGET("/now_playing"))
if (enable) {
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
}
} }
/** /**
@@ -797,11 +787,10 @@ def cb_boseSetPowerState(xml, state) {
*/ */
def cb_boseConfirmPowerOn(xml, tries) { def cb_boseConfirmPowerOn(xml, tries) {
def result = [] def result = []
def attempt = tries as Integer log.warn "boseConfirmPowerOn() attempt #" + tries
log.warn "boseConfirmPowerOn() attempt #$attempt" if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
result << boseRefreshNowPlaying() result << boseRefreshNowPlaying()
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1) queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
} }
return result return result
} }

View File

@@ -21,9 +21,7 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer" fingerprint inClusters: "0x26"
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
} }
simulator { simulator {
@@ -44,10 +42,6 @@ metadata {
reply "200163,delay 5000,2602": "command: 2603, payload: 63" reply "200163,delay 5000,2602": "command: 2603, payload: 63"
} }
preferences {
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
}
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -76,28 +70,11 @@ metadata {
} }
main(["switch"]) main(["switch"])
details(["switch", "level", "refresh"]) details(["switch", "level", "indicator", "refresh"])
} }
} }
def updated(){
switch (ledIndicator) {
case "on":
indicatorWhenOn()
break
case "off":
indicatorWhenOff()
break
case "never":
indicatorNever()
break
default:
indicatorWhenOn()
break
}
}
def parse(String description) { def parse(String description) {
def result = null def result = null
if (description != "updated") { if (description != "updated") {
@@ -225,19 +202,19 @@ def refresh() {
delayBetween(commands,100) delayBetween(commands,100)
} }
void indicatorWhenOn() { def indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false) sendEvent(name: "indicatorStatus", value: "when on")
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
} }
void indicatorWhenOff() { def indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false) sendEvent(name: "indicatorStatus", value: "when off")
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
} }
void indicatorNever() { def indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false) sendEvent(name: "indicatorStatus", value: "never")
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
} }
def invertSwitch(invert=true) { def invertSwitch(invert=true) {

View File

@@ -13,7 +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 physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -144,14 +143,51 @@ private Map parseReportAttributeMessage(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
Map resultMap = [:] String msgCode = parsedMsg[2]
result.name = 'motion' Map resultMap = [:]
result.value = zs.isAlarm2Set() ? 'active' : 'inactive' switch(msgCode) {
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion') case '0x0030': // Closed/No Motion/Dry
log.debug 'no motion'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
return resultMap case '0x0032': // Open/Motion/Wet
log.debug 'motion'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0032': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0033': // Battery Alarm
break
case '0x0034': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
case '0x0035': // Restore Report
break
case '0x0036': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0038': // Test Mode
break
}
return resultMap
} }
def refresh() def refresh()

View File

@@ -14,9 +14,6 @@
* *
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") { definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery" capability "Battery"
@@ -222,33 +219,40 @@ private Map parseReportAttributeMessage(String description) {
} }
private List parseIasMessage(String description) { private List parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(" ")
log.debug "parseIasMessage: $description" String msgCode = parsedMsg[2]
List resultListMap = [] List resultListMap = []
Map resultMap_battery = [:] Map resultMap_battery = [:]
Map resultMap_battery_state = [:] Map resultMap_battery_state = [:]
Map resultMap_sensor = [:] Map resultMap_sensor = [:]
resultMap_sensor.name = "contact" // Relevant bit field definitions from ZigBee spec
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed" def BATTERY_BIT = ( 1 << 3 )
def TROUBLE_BIT = ( 1 << 6 )
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
// Convert hex string to integer
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
// Check each relevant bit, create map for it, and add to list // Check each relevant bit, create map for it, and add to list
log.debug "parseIasMessage: Battery Status ${zs.battery}" log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
log.debug "parseIasMessage: Trouble Status ${zs.trouble}" log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}" log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
/* Comment out this path to check the battery state to avoid overwriting the /* Comment out this path to check the battery state to avoid overwriting the
battery value (Change log #2), but keep these conditions for later use battery value (Change log #2), but keep these conditions for later use
resultMap_battery_state.name = "battery_state" resultMap_battery_state.name = "battery_state"
if (zs.isTroubleSet()) { if (zoneStatus & TROUBLE_BIT) {
resultMap_battery_state.value = "failed" resultMap_battery_state.value = "failed"
resultMap_battery.name = "battery" resultMap_battery.name = "battery"
resultMap_battery.value = 0 resultMap_battery.value = 0
} }
else { else {
if (zs.isBatterySet()) { if (zoneStatus & BATTERY_BIT) {
resultMap_battery_state.value = "low" resultMap_battery_state.value = "low"
// to generate low battery notification by the platform // to generate low battery notification by the platform
@@ -266,6 +270,9 @@ private List parseIasMessage(String description) {
} }
*/ */
resultMap_sensor.name = "contact"
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
resultListMap << resultMap_battery_state resultListMap << resultMap_battery_state
resultListMap << resultMap_battery resultListMap << resultMap_battery
resultListMap << resultMap_sensor resultListMap << resultMap_sensor

View File

@@ -101,12 +101,6 @@ def parse(String description) {
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastOnOff == null){
state.lastOnOff = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
}
state.lastOnOff = now()
} }
} }
else { else {
@@ -122,24 +116,9 @@ def off() {
def on() { def on() {
zigbee.on() zigbee.on()
} }
/**
* PING is used by Device-Watch in attempt to reach the Outlet
* */
def ping() {
// send read attribute onOFF if the last time we heard from the outlet is outside of the checkInterval
if (state.lastOnOff < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastOnOff=${new Date(state.lastOnOff)}"
state.lastOnOff = null
return zigbee.onOffRefresh()
} else { // if the last onOff activity is within the checkInterval we artificially create a Device-Watch event
log.info "ping, alive=yes, lastOnOff=${new Date(state.lastOnOff)}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh() zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
} }
def configure() { def configure() {

View File

@@ -13,8 +13,6 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -24,7 +22,6 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Water Sensor" capability "Water Sensor"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -172,9 +169,42 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -225,8 +255,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }

View File

@@ -13,8 +13,6 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -24,7 +22,6 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Refresh" capability "Refresh"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -185,10 +182,44 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion Map resultMap = [:]
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -240,8 +271,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }

View File

@@ -15,7 +15,6 @@
*/ */
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH //DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -169,8 +168,44 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive') String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -205,8 +240,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }

View File

@@ -13,7 +13,6 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -225,13 +224,47 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:] Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
if(garageSensor != "Yes") { case '0x0021': // Open/Motion/Wet
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
case '0x0025': // Restore Report
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
} }
return resultMap return resultMap
} }
@@ -305,8 +338,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
@@ -371,21 +403,39 @@ def refresh() {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings " log.debug "Refreshing Values for manufacturer: SmartThings "
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276) refreshCmds = refreshCmds + [
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. /* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
Separating these out in a separate if-else because I do not want to touch Centralite part seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
as of now. Separating these out in a separate if-else because I do not want to touch Centralite part
*/ as of now.
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode]) */
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else { } else {
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode]) refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} }
//Common refresh commands //Common refresh commands
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) + refreshCmds = refreshCmds + [
zigbee.readAttribute(0x0001, 0x0020) + "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse() return refreshCmds + enrollResponse()
} }
@@ -393,15 +443,38 @@ def refresh() {
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
def configCmds = enrollResponse() + def configCmds = [
zigbee.batteryConfig() + "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
zigbee.temperatureConfig() + "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() return configCmds + refresh()
} }

View File

@@ -14,7 +14,6 @@
* *
*/ */
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -25,7 +24,6 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
} }
@@ -173,9 +171,40 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -205,8 +234,7 @@ def getTemperature(value) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -13,17 +13,14 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -168,8 +165,40 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -199,8 +228,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -245,7 +273,6 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -21,7 +21,6 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Health Check" capability "Health Check"
capability "Sensor"
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003" fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
} }
@@ -206,8 +205,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -16,8 +16,6 @@
metadata { metadata {
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
capability "Alarm" capability "Alarm"
capability "Sensor"
capability "Actuator"
} }
simulator { simulator {

View File

@@ -1,8 +1,6 @@
metadata { metadata {
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
capability "Color Control" capability "Color Control"
capability "Sensor"
capability "Actuator"
} }
simulator { simulator {

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Contact Sensor" capability "Contact Sensor"
capability "Sensor"
command "open" command "open"
command "close" command "close"

View File

@@ -15,8 +15,6 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
capability "Lock" capability "Lock"
capability "Sensor"
capability "Actuator"
} }
// Simulated lock // Simulated lock

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Motion Sensor" capability "Motion Sensor"
capability "Sensor"
command "active" command "active"
command "inactive" command "inactive"

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Presence Sensor" capability "Presence Sensor"
capability "Sensor"
command "arrived" command "arrived"
command "departed" command "departed"

View File

@@ -16,8 +16,6 @@ metadata {
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
capability "Switch" capability "Switch"
capability "Relay Switch" capability "Relay Switch"
capability "Sensor"
capability "Actuator"
command "onPhysical" command "onPhysical"
command "offPhysical" command "offPhysical"

View File

@@ -16,7 +16,6 @@ metadata {
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Switch Level" capability "Switch Level"
capability "Sensor"
command "up" command "up"
command "down" command "down"

View File

@@ -16,8 +16,6 @@ metadata {
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat" capability "Thermostat"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Sensor"
capability "Actuator"
command "tempUp" command "tempUp"
command "tempDown" command "tempDown"

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Water Sensor" capability "Water Sensor"
capability "Sensor"
command "wet" command "wet"
command "dry" command "dry"

View File

@@ -13,7 +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 physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -162,9 +161,40 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -193,8 +223,7 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) result.value = Math.min(100, (int) pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -1,7 +1,8 @@
/** /**
* ZigBee Button * Iris Smart Fob
* *
* Copyright 2015 Mitch Pond * Copyright 2015 Mitch Pond
* Presence code adapted from SmartThings Arrival Sensor HA device type
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -13,235 +14,181 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
metadata { metadata {
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") { definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Actuator" capability "Battery"
capability "Battery" capability "Button"
capability "Button"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Presence Sensor"
capability "Sensor" capability "Sensor"
command "enrollResponse" //fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
}
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch" preferences{
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant" input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button" defaultValue: 3, displayDuringSetup: false)
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob" input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
input "logging", "bool", title: "Enable debug logging",
defaultValue: false, displayDuringSetup: false
} }
simulator {} tiles(scale: 2) {
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
preferences { state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
section { state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 1, displayDuringSetup: false)
} }
} standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
tiles {
standardTile("button", "device.button", width: 2, height: 2) {
state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#79b821"
} }
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) { main (["presence"])
state "battery", label:'${currentValue}% battery', unit:"" details(["presence","button","battery"])
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["button"])
details(["button", "battery", "refresh"])
}
} }
def parse(String description) { def parse(String description) {
log.debug "description is $description" def descMap = zigbee.parseDescriptionAsMap(description)
def event = zigbee.getEvent(description) logIt descMap
if (event) { state.lastCheckin = now()
sendEvent(event) logIt "lastCheckin = ${state.lastCheckin}"
} handlePresenceEvent(true)
else {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
event = getBatteryResult(zigbee.convertHexToInt(descMap.value))
}
else if (descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) {
event = parseNonIasButtonMessage(descMap)
}
}
else if (description?.startsWith('zone status')) {
event = parseIasButtonMessage(description)
}
log.debug "Parse returned $event" def results = []
def result = event ? createEvent(event) : [] if (description?.startsWith('catchall:'))
results = parseCatchAllMessage(descMap)
else if (description?.startsWith('read attr -'))
results = parseReportAttributeMessage(descMap)
else logIt(descMap, "trace")
if (description?.startsWith('enroll request')) { return results;
List cmds = enrollResponse()
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
}
private Map parseIasButtonMessage(String description) {
int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
if (zoneInt & 0x02) {
resultMap = getButtonResult('press')
} else {
resultMap = getButtonResult('release')
}
return resultMap
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def volts = rawValue / 10
if (volts > 3.0 || volts == 0 || rawValue == 0xFF) {
return [:]
}
else {
def result = [
name: 'battery'
]
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def linkText = getLinkText(device)
result.descriptionText = "${linkText} battery was ${result.value}%"
return result
}
}
private Map parseNonIasButtonMessage(Map descMap){
def buttonState = ""
def buttonNumber = 0
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
&&(descMap.clusterInt == 0x0006)) {
if (descMap.command == "01") {
getButtonResult("press")
}
else if (descMap.command == "00") {
getButtonResult("release")
}
}
else if (descMap.clusterInt == 0x0006) {
buttonState = "pushed"
if (descMap.command == "01") {
buttonNumber = 1
}
else if (descMap.command == "00") {
buttonNumber = 2
}
if (buttonNumber !=0) {
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
else {
return [:]
}
}
else if (descMap.clusterInt == 0x0008) {
if (descMap.command == "05") {
state.buttonNumber = 1
getButtonResult("press", 1)
}
else if (descMap.command == "01") {
state.buttonNumber = 2
getButtonResult("press", 2)
}
else if (descMap.command == "03") {
getButtonResult("release", state.buttonNumber)
}
}
}
def refresh() {
log.debug "Refreshing Battery"
return zigbee.readAttribute(0x0001, 0x20) +
zigbee.enrollResponse()
}
def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def cmds = []
if (device.getDataValue("model") == "3450-L") {
cmds << [
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 300"
]
}
return zigbee.onOffConfig() +
zigbee.levelConfig() +
zigbee.configureReporting(0x0001, 0x20, 0x20, 30, 21600, 0x01) +
zigbee.enrollResponse() +
zigbee.readAttribute(0x0001, 0x20) +
cmds
}
private Map getButtonResult(buttonState, buttonNumber = 1) {
if (buttonState == 'release') {
log.debug "Button was value : $buttonState"
def timeDiff = now() - state.pressTime
log.info "timeDiff: $timeDiff"
def holdPreference = holdTime ?: 1
log.info "holdp1 : $holdPreference"
holdPreference = (holdPreference as int) * 1000
log.info "holdp2 : $holdPreference"
if (timeDiff > 10000) { //timeDiff>10sec check for refresh sending release value causing actions to be executed
return [:]
}
else {
if (timeDiff < holdPreference) {
buttonState = "pushed"
}
else {
buttonState = "held"
}
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
}
else if (buttonState == 'press') {
log.debug "Button was value : $buttonState"
state.pressTime = now()
log.info "presstime: ${state.pressTime}"
return [:]
}
}
def installed() {
initialize()
} }
def updated() { def updated() {
initialize() startTimer()
configure()
} }
def initialize() { def configure(){
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) { logIt "Configuring Smart Fob..."
sendEvent(name: "numberOfButtons", value: 2) [
} "zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
else if ((device.getDataValue("manufacturer") == "CentraLite") && "zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
((device.getDataValue("model") == "3455-L") || (device.getDataValue("model") == "3460-L"))) { "zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
sendEvent(name: "numberOfButtons", value: 1) "zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
} "zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
else if ((device.getDataValue("manufacturer") == "CentraLite") && (device.getDataValue("model") == "3450-L")) { ] +
sendEvent(name: "numberOfButtons", value: 4) zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
}
else {
//default. can be changed
sendEvent(name: "numberOfButtons", value: 4)
}
} }
def parseCatchAllMessage(descMap) {
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
handleButtonPress(descMap.sourceEndpoint as int)
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
handleButtonRelease(descMap.sourceEndpoint as int)
else logIt("Parse: Unhandled message: ${descMap}","trace")
}
def parseReportAttributeMessage(descMap) {
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
else logIt descMap
}
private createBatteryEvent(percent) {
logIt "Battery level at " + percent
return createEvent([name: "battery", value: percent])
}
//this method determines if a press should count as a push or a hold and returns the relevant event type
private handleButtonRelease(button) {
logIt "lastPress state variable: ${state.lastPress}"
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
if (!state.lastPress) return sequenceError()
else if (state.lastPress.button != button) return sequenceError()
def currentTime = now()
def startOfPress = state.lastPress?.time
def timeDif = currentTime - startOfPress
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
if (timeDif < 0)
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
return sequenceError()
else if (timeDif < holdTimeMillisec)
return createButtonEvent(button,"pushed")
else
return createButtonEvent(button,"held")
}
private handleButtonPress(button) {
state.lastPress = [button: button, time: now()]
}
private createButtonEvent(button,action) {
logIt "Button ${button} ${action}"
return createEvent([
name: "button",
value: action,
data:[buttonNumber: button],
descriptionText: "${device.displayName} button ${button} was ${action}",
isStateChange: true,
displayed: true])
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
logIt "Sensor is present"
startTimer()
} else if (!present) {
logIt "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
logIt "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
logIt "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
logIt "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}
// ****** Utility functions ******
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }

View File

@@ -20,7 +20,6 @@ metadata {
capability "Switch" capability "Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
} }
// simulator metadata // simulator metadata

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright 2016 SmartThings * Copyright 2015 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -11,133 +11,100 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
/*
* Capabilities
* - Battery
* - Configuration
* - Refresh
* - Switch
* - Valve
*/
metadata { metadata {
definition (name: "ZigBee Valve", namespace: "smartthings", author: "SmartThings") { definition (name: "Zigbee Valve", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Battery"
capability "Battery" capability "Configuration"
capability "Configuration" capability "Refresh"
capability "Power Source" capability "Switch"
capability "Refresh" capability "Valve"
capability "Valve"
fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0006, 0020, 0B02, FC02", outClusters: "0019", manufacturer: "WAXMAN", model: "leakSMART Water Valve v2.10", deviceJoinName: "leakSMART Valve" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0020,0006,0B02", outClusters: "0003"
fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0008, 000F, 0020, 0B02", outClusters: "0003, 0019", manufacturer: "WAXMAN", model: "House Water Valve - MDL-TBD", deviceJoinName: "Waxman House Water Valve" }
}
// simulator metadata // simulator metadata
simulator { simulator {
// status messages // status messages
status "on": "on/off: 1" status "on": "on/off: 1"
status "off": "on/off: 0" status "off": "on/off: 0"
// reply messages // reply messages
reply "zcl on-off on": "on/off: 1" reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0" reply "zcl on-off off": "on/off: 0"
} }
tiles(scale: 2) { // UI tile definitions
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){ tiles {
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing" state "off", label: 'closed', action: "switch.on", icon: "st.Outdoor.outdoor16", backgroundColor: "#e86d13"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening" state "on", label: 'open', action: "switch.off", icon: "st.Outdoor.outdoor16", backgroundColor: "#53a7c0"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing" }
attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening" main "switch"
} details(["switch"])
tileAttribute ("powerSource", key: "SECONDARY_CONTROL") { }
attributeState "powerSource", label:'Power Source: ${currentValue}'
}
}
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["valve"])
details(["valve", "battery", "refresh"])
}
} }
private getCLUSTER_BASIC() { 0x0000 }
private getBASIC_ATTR_POWER_SOURCE() { 0x0007 }
private getCLUSTER_POWER() { 0x0001 }
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
private getTYPE_U8() { 0x20 }
private getTYPE_ENUM8() { 0x30 }
// Parse incoming device messages to generate events // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.info description
def event = zigbee.getEvent(description) if (description?.startsWith("catchall:")) {
if (event) { def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
if(event.name == "switch") { def result = createEvent(name: name, value: value)
event.name = "contact" //0006 cluster in valve is tied to contact def msg = zigbee.parse(description)
if(event.value == "on") { log.debug "Parse returned ${result?.descriptionText}"
event.value = "open" return result
} log.trace msg
else if(event.value == "off") { log.trace "data: $msg.data"
event.value = "closed" }
} else {
} def name = description?.startsWith("on/off: ") ? "switch" : null
sendEvent(event) def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
} def result = createEvent(name: name, value: value)
else { log.debug "Parse returned ${result?.descriptionText}"
def descMap = zigbee.parseDescriptionAsMap(description) return result
if (descMap.clusterInt == CLUSTER_BASIC && descMap.attrInt == BASIC_ATTR_POWER_SOURCE){ }
def value = descMap.value }
if (value == "01" || value == "02") {
sendEvent(name: "powerSource", value: "Mains") // Commands to device
} def on() {
else if (value == "03") { log.debug "on()"
sendEvent(name: "powerSource", value: "Battery") sendEvent(name: "switch", value: "on")
} "st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
else if (value == "04") { }
sendEvent(name: "powerSource", value: "DC")
} def off() {
else { log.debug "off()"
sendEvent(name: "powerSource", value: "Unknown") sendEvent(name: "switch", value: "off")
} "st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
}
else if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
event.name = "battery"
event.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug descMap
}
}
} }
def open() { def open() {
zigbee.on() log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
} }
def close() { def close() {
zigbee.off() log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
} }
def refresh() { def refresh() {
log.debug "refresh called" log.debug "sending refresh command"
zigbee.onOffRefresh() + "st rattr 0x${device.deviceNetworkId} 1 6 0"
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) +
zigbee.onOffConfig() +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + "zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}"
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
} }

View File

@@ -1,205 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
status "09%": "command: 2003, payload: 09"
status "10%": "command: 2003, payload: 0A"
status "33%": "command: 2003, payload: 21"
status "66%": "command: 2003, payload: 42"
status "99%": "command: 2003, payload: 63"
// reply messages
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
}
main(["switch"])
details(["switch", "level", "refresh"])
}
}
def parse(String description) {
def result = null
if (description != "updated") {
log.debug "parse() >> zwave.parse($description)"
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
result = [result, response(zwave.basicV1.basicGet())]
log.debug "Was hailed: requesting state update"
} else {
log.debug "Parse returned ${result?.descriptionText}"
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
dimmerEvents(cmd)
}
private dimmerEvents(physicalgraph.zwave.Command cmd) {
def value = (cmd.value ? "on" : "off")
def result = [createEvent(name: "switch", value: value)]
if (cmd.value && cmd.value <= 100) {
result << createEvent(name: "level", value: cmd.value, unit: "%")
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
log.debug "ConfigurationReport $cmd"
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
createEvent([name: "indicatorStatus", value: value])
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
def on() {
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()
],5000)
}
def off() {
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()
],5000)
}
def setLevel(value) {
log.debug "setLevel >> value: $value"
def valueaux = value as Integer
def level = Math.max(Math.min(valueaux, 99), 0)
if (level > 0) {
sendEvent(name: "switch", value: "on")
} else {
sendEvent(name: "switch", value: "off")
}
sendEvent(name: "level", value: level, unit: "%")
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
def setLevel(value, duration) {
log.debug "setLevel >> value: $value, duration: $duration"
def valueaux = value as Integer
def level = Math.max(Math.min(valueaux, 99), 0)
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
}
def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
def refresh() {
log.debug "refresh() is called"
def commands = []
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
if (getDataValue("MSR") == null) {
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
}
delayBetween(commands,100)
}
def invertSwitch(invert=true) {
if (invert) {
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
}
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}

View File

@@ -28,6 +28,7 @@ metadata {
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+ fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
} }
// simulator metadata // simulator metadata
@@ -35,7 +36,6 @@ metadata {
// status messages // status messages
status "open": "command: 2001, payload: FF" status "open": "command: 2001, payload: FF"
status "closed": "command: 2001, payload: 00" status "closed": "command: 2001, payload: 00"
status "wake up": "command: 8407, payload: "
} }
// UI tile definitions // UI tile definitions
@@ -81,7 +81,7 @@ def updated() {
def cmds = [] def cmds = []
if (!state.MSR) { if (!state.MSR) {
cmds = [ cmds = [
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()), zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
"delay 1200", "delay 1200",
zwave.wakeUpV1.wakeUpNoMoreInformation().format() zwave.wakeUpV1.wakeUpNoMoreInformation().format()
] ]
@@ -94,9 +94,9 @@ def updated() {
} }
def configure() { def configure() {
commands([ delayBetween([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(), zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
zwave.batteryV1.batteryGet() batteryGetCommand()
], 6000) ], 6000)
} }
@@ -147,11 +147,12 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
result << sensorValueEvent(1) result << sensorValueEvent(1)
} else if (cmd.event == 0x03) { } else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true) result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
} else if (cmd.event == 0x05 || cmd.event == 0x06) { } else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) { } else if (cmd.event == 0x07) {
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion") result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
} }
} else if (cmd.notificationType) { } else if (cmd.notificationType) {
@@ -169,11 +170,12 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false) def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
def cmds = [] def cmds = []
if (!state.MSR) { if (!state.MSR) {
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
cmds << "delay 1200" cmds << "delay 1200"
} }
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) { if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
cmds << command(zwave.batteryV1.batteryGet()) cmds << batteryGetCommand()
} else { } else {
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format() cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
} }
@@ -210,7 +212,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
if (msr == "0086-0102-0059") { if (msr == "0086-0102-0059") {
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()) result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
} else { } else {
result << response(command(zwave.batteryV1.batteryGet())) result << response(batteryGetCommand())
} }
} }
@@ -218,7 +220,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
} }
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1]) def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
// log.debug "encapsulated: $encapsulatedCommand" // log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) { if (encapsulatedCommand) {
state.sec = 1 state.sec = 1
@@ -230,16 +232,12 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false) createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
} }
private command(physicalgraph.zwave.Command cmd) { def batteryGetCommand() {
if (state.sec == 1) { def cmd = zwave.batteryV1.batteryGet()
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() if (state.sec) {
} else { cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
cmd.format()
} }
} cmd.format()
private commands(commands, delay=200) {
delayBetween(commands.collect{ command(it) }, delay)
} }
def retypeBasedOnMSR() { def retypeBasedOnMSR() {
@@ -262,12 +260,8 @@ def retypeBasedOnMSR() {
setDeviceType("3-in-1 Multisensor Plus (SG)") setDeviceType("3-in-1 Multisensor Plus (SG)")
break break
case "0109-2001-0106": // Vision door/window case "0109-2001-0106": // Vision door/window
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor" log.debug "Changing device type to Door / Window Sensor Plus (SG)"
setDeviceType("Z-Wave Plus Door/Window Sensor") setDeviceType("Door / Window Sensor Plus (SG)")
break
case "0109-2002-0205": // Vision Motion
log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
setDeviceType("Z-Wave Plus Motion/Temp Sensor")
break break
} }
} }

View File

@@ -21,13 +21,6 @@ metadata {
capability "Motion Sensor" capability "Motion Sensor"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
} }
simulator { simulator {
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
} }
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) { if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
result << response(zwave.batteryV1.batteryGet()) result << response(zwave.batteryV1.batteryGet())
} else { result << response("delay 1200")
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
} }
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
result result
} }

View File

@@ -1,270 +0,0 @@
/**
* Copyright 2016 SmartThings
* Copyright 2015 AstraLink
*
* 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.
*
* Z-Wave Plus Door/Window Sensor, ZD2102*-5
*
*/
metadata {
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Contact Sensor"
capability "Configuration"
capability "Battery"
capability "Sensor"
// for Astralink
attribute "ManufacturerCode", "string"
attribute "ProduceTypeCode", "string"
attribute "ProductCode", "string"
attribute "WakeUp", "string"
attribute "WirelessConfig", "string"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x70, 0x84, 0x7A"
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,71"
fingerprint mfr:"0109", prod:"2001", model:"0106" // not using deviceJoinName because it's sold under different brand names
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["contact"])
details(["contact","battery"])
}
simulator {
// messages the device returns in response to commands it receives
status "open (basic)" : "command: 9881, payload: 00 20 01 FF"
status "closed (basic)" : "command: 9881 payload: 00 20 01 00"
status "open (notification)" : "command: 9881, payload: 00 71 05 06 FF 00 FF 06 16 00 00"
status "closed (notification)" : "command: 9881, payload: 00 71 05 06 00 00 FF 06 17 00 00"
status "tamper: enclosure opened" : "command: 9881, payload: 00 71 05 07 FF 00 FF 07 03 00 00"
status "tamper: enclosure replaced" : "command: 9881, payload: 00 71 05 07 00 00 FF 07 00 00 00"
status "wake up" : "command: 9881, payload: 00 84 07"
status "battery (100%)" : "command: 9881, payload: 00 80 03 64"
status "battery low" : "command: 9881, payload: 00 80 03 FF"
}
}
def configure() {
log.debug "configure()"
def cmds = []
if (state.sec != 1) {
// secure inclusion may not be complete yet
cmds << "delay 1000"
}
cmds += secureSequence([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
zwave.batteryV1.batteryGet(),
], 500)
cmds << "delay 8000"
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return cmds
}
private getCommandClassVersions() {
[
0x71: 3, // Notification
0x5E: 2, // ZwaveplusInfo
0x59: 1, // AssociationGrpInfo
0x85: 2, // Association
0x20: 1, // Basic
0x80: 1, // Battery
0x70: 1, // Configuration
0x5A: 1, // DeviceResetLocally
0x7A: 2, // FirmwareUpdateMd
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x98: 1, // Security
0x84: 2, // WakeUp
0x86: 1, // Version
]
}
// Parse incoming device messages to generate events
def parse(String description) {
def result = []
def cmd
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description.startsWith("Err")) {
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
} else {
cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result instanceof List) {
result = result.flatten()
}
log.debug "Parsed '$description' to $result"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) {
state.sec = 1
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
return [createEvent(descriptionText: cmd.toString())]
}
}
def sensorValueEvent(value) {
if (value) {
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
} else {
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
}
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
return sensorValueEvent(cmd.sensorValue)
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
return sensorValueEvent(cmd.sensorState)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def result = []
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
result << sensorValueEvent(1)
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
result << sensorValueEvent(0)
} else if (cmd.notificationType == 0x07) {
if (cmd.event == 0x00) {
if (cmd.eventParametersLength == 0 || cmd.eventParameter[0] != 3) {
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
} else {
result << sensorValueEvent(0)
}
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
if (!device.currentState("ManufacturerCode")) {
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
}
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else {
result << createEvent(descriptionText: "$device.displayName event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
}
} else if (cmd.notificationType) {
result << createEvent(descriptionText: "$device.displayName notification $cmd.notificationType event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
def cmds = []
if (!device.currentState("ManufacturerCode")) {
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmds << "delay 2000"
}
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
event.descriptionText += ", requesting battery"
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
cmds << "delay 800"
cmds << secure(zwave.batteryV1.batteryGet())
cmds << "delay 2000"
} else {
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
}
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return [event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
def event = createEvent(map)
// Save at least one battery report in events list every few days
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
map.isStateChange = true
}
state.lastbat = now()
return [event]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def result = []
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
def productTypeCode = String.format("%04X", cmd.productTypeId)
def productCode = String.format("%04X", cmd.productId)
def wirelessConfig = "ZWP"
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
result << createEvent(name: "ProductCode", value: productCode)
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
return result
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
}
private secure(physicalgraph.zwave.Command cmd) {
if (state.sec == 0) { // default to secure
cmd.format()
} else {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
}
private secureSequence(commands, delay=200) {
delayBetween(commands.collect{ secure(it) }, delay)
}

View File

@@ -1,327 +0,0 @@
/**
* Copyright 2016 SmartThings
* Copyright 2015 AstraLink
*
* 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.
*
* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5
*
*/
metadata {
definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
capability "Temperature Measurement"
capability "Configuration"
capability "Battery"
capability "Sensor"
// for Astralink
attribute "ManufacturerCode", "string"
attribute "ProduceTypeCode", "string"
attribute "ProductCode", "string"
attribute "WakeUp", "string"
attribute "WirelessConfig", "string"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A"
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71"
fingerprint mfr:"0109", prod:"2002", model:"0205" // not using deviceJoinName because it's sold under different brand names
}
tiles {
standardTile("motion", "device.motion", width: 3, height: 2) {
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
}
valueTile("temperature", "device.temperature", inactiveLabel: false) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:"%"
}
main(["motion", "temperature"])
details(["motion", "temperature", "battery"])
}
}
def updated() {
if (!device.currentState("ManufacturerCode")) {
response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
}
}
def configure() {
log.debug "configure()"
def cmds = []
if (state.sec != 1) {
// secure inclusion may not be complete yet
cmds << "delay 1000"
}
cmds += secureSequence([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
zwave.batteryV1.batteryGet(),
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
], 500)
cmds << "delay 8000"
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return cmds
}
private getCommandClassVersions() {
[
0x71: 3, // Notification
0x5E: 2, // ZwaveplusInfo
0x59: 1, // AssociationGrpInfo
0x85: 2, // Association
0x20: 1, // Basic
0x80: 1, // Battery
0x70: 1, // Configuration
0x5A: 1, // DeviceResetLocally
0x7A: 2, // FirmwareUpdateMd
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x98: 1, // Security
0x31: 5, // SensorMultilevel
0x84: 2 // WakeUp
]
}
// Parse incoming device messages to generate events
def parse(String description) {
def result = []
def cmd
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description.startsWith("Err")) {
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
} else {
cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result instanceof List) {
result = result.flatten()
}
log.debug "Parsed '$description' to $result"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) {
state.sec = 1
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
return [createEvent(descriptionText: cmd.toString())]
}
}
def sensorValueEvent(value) {
def result = []
if (value) {
log.debug "sensorValueEvent($value) : active"
result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
} else {
log.debug "sensorValueEvent($value) : inactive"
result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped")
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
return sensorValueEvent(cmd.sensorValue)
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
return sensorValueEvent(cmd.sensorState)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def result = []
if (cmd.notificationType == 0x07) {
if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x08) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x00) {
if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) {
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
} else {
result << sensorValueEvent(0)
}
} else if (cmd.event == 0xFF) {
result << sensorValueEvent(1)
} else {
result << createEvent(descriptionText: "$device.displayName sent event $cmd.event")
}
} else if (cmd.notificationType) {
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
def cmds = []
if (!device.currentState("ManufacturerCode")) {
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmds << "delay 2000"
}
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
event.descriptionText += ", requesting battery"
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
cmds << "delay 800"
cmds << secure(zwave.batteryV1.batteryGet())
cmds << "delay 2000"
} else {
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
}
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return [event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def result = []
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
def event = createEvent(map)
// Save at least one battery report in events list every few days
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
map.isStateChange = true
}
state.lastbat = now()
return [event]
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
def result = []
def map = [:]
switch (cmd.sensorType) {
case 1:
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
break;
case 3:
map.name = "illuminance"
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = "lux"
break;
case 5:
map.name = "humidity"
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = cmd.scale == 0 ? "%" : ""
break;
case 0x1E:
map.name = "loudness"
map.unit = cmd.scale == 1 ? "dBA" : "dB"
map.value = cmd.scaledSensorValue.toString()
break;
default:
map.descriptionText = cmd.toString()
}
result << createEvent(map)
return result
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def result = []
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
def productTypeCode = String.format("%04X", cmd.productTypeId)
def productCode = String.format("%04X", cmd.productId)
def wirelessConfig = "ZWP"
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
result << createEvent(name: "ProductCode", value: productCode)
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
if (manufacturerCode == "0109" && productTypeCode == "2002") {
result << response(secureSequence([
// Change re-trigger duration to 1 minute
zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1),
zwave.batteryV1.batteryGet(),
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
], 400))
}
return result
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
}
private secure(physicalgraph.zwave.Command cmd) {
if (state.sec == 0) { // default to secure
cmd.format()
} else {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
}
private secureSequence(commands, delay=200) {
delayBetween(commands.collect{ secure(it) }, delay)
}

View File

@@ -1,143 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
}
// simulator metadata
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
// reply messages
reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
reply "200100,delay 100,2502": "command: 2503, payload: 00"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
}
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh"])
}
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
result = [result, response(zwave.basicV1.basicGet())]
log.debug "Was hailed: requesting state update"
} else {
log.debug "Parse returned ${result?.descriptionText}"
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
[name: "indicatorStatus", value: value, display: false]
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
def on() {
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def off() {
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def poll() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def invertSwitch(invert=true) {
if (invert) {
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
}
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}

View File

@@ -15,15 +15,12 @@ metadata {
definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") { definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Indicator" capability "Indicator"
capability "Switch" capability "Switch"
capability "Polling" capability "Polling"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch" fingerprint inClusters: "0x25"
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
} }
// simulator metadata // simulator metadata
@@ -36,10 +33,6 @@ metadata {
reply "200100,delay 100,2502": "command: 2503, payload: 00" reply "200100,delay 100,2502": "command: 2503, payload: 00"
} }
preferences {
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
}
// tile definitions // tile definitions
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
@@ -59,27 +52,10 @@ metadata {
} }
main "switch" main "switch"
details(["switch","refresh"]) details(["switch","refresh","indicator"])
} }
} }
def updated(){
switch (ledIndicator) {
case "on":
indicatorWhenOn()
break
case "off":
indicatorWhenOff()
break
case "never":
indicatorNever()
break
default:
indicatorWhenOn()
break
}
}
def parse(String description) { def parse(String description) {
def result = null def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1]) def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
@@ -163,19 +139,19 @@ def refresh() {
]) ])
} }
void indicatorWhenOn() { def indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false) sendEvent(name: "indicatorStatus", value: "when on", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
} }
void indicatorWhenOff() { def indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false) sendEvent(name: "indicatorStatus", value: "when off", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
} }
void indicatorNever() { def indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false) sendEvent(name: "indicatorStatus", value: "never", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format())) zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
} }
def invertSwitch(invert=true) { def invertSwitch(invert=true) {

View File

@@ -42,7 +42,7 @@
* Code * Code
***************************************************************** *****************************************************************
*/ */
// for the UI // for the UI
metadata { metadata {
definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") { definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") {
capability "Switch" capability "Switch"
@@ -55,25 +55,23 @@ metadata {
command "levelUp" command "levelUp"
command "levelDown" command "levelDown"
command "on" command "on"
command "off" command "off"
command "setBulbPower" command "setBulbPower"
} }
simulator { simulator {
// TODO: define status and reply messages here // TODO: define status and reply messages here
} }
preferences { preferences {
input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5 input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5
} }
tiles { tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" state "on", label:'${name}', action:"off", icon:"st.switches.light.on", backgroundColor:"#79b821"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" state "off", label:'${name}', action:"on", icon:"st.switches.light.off", backgroundColor:"#ffffff"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
} }
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
@@ -84,15 +82,15 @@ metadata {
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%' state "level", label: 'Level ${currentValue}%'
} }
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright" state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
} }
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light" state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
} }
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") { valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
state "power", label: '${currentValue} Watts' state "power", label: '${currentValue} Watts'
} }
main(["switch"]) main(["switch"])
details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ]) details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ])
@@ -104,8 +102,8 @@ def parse(description) {
//log.debug "parse() - $description" //log.debug "parse() - $description"
def results = [] def results = []
if ( description == "updated" ) if ( description == "updated" )
return return
if (description?.name && description?.value) if (description?.name && description?.value)
{ {
@@ -116,73 +114,73 @@ def parse(description) {
// handle commands // handle commands
def setBulbPower(value) { def setBulbPower(value) {
state.bulbPower = value state.bulbPower = value
log.debug "In child with bulbPower of ${state.bulbPower}" log.debug "In child with bulbPower of ${state.bulbPower}"
} }
def on() { def on() {
log.debug "Executing 'on'" log.debug "Executing 'on'"
sendEvent(name:"switch",value:on) sendEvent(name:"switch",value:on)
parent.on(this) parent.on(this)
def levelSetting = device.latestValue("level") as Float ?: 1.0 def levelSetting = device.latestValue("level") as Float ?: 1.0
def bulbPowerMax = device.latestValue("setBulbPower") as Float //def bulbPowerMax = device.latestValue("setBulbPower") as Float
def calculatedPower = bulbPowerMax * (levelSetting / 100) //def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent(name: "power", value: calculatedPower.round(1)) //sendEvent(name: "power", value: calculatedPower.round(1))
if (device.latestValue("level") == null) { if (device.latestValue("level") == null) {
sendEvent( name: "level", value: 1.0 ) sendEvent( name: "level", value: 1.0 )
} }
} }
def off() { def off() {
log.debug "Executing 'off'" log.debug "Executing 'off'"
sendEvent(name:"switch",value:off) sendEvent(name:"switch",value:off)
parent.off(this) parent.off(this)
sendEvent(name: "power", value: 0.0) sendEvent(name: "power", value: 0.0)
} }
def levelUp() { def levelUp() {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float def step = state.stepsize as float
level+= step level+= step
if ( level > 100 ) if ( level > 100 )
level = 100 level = 100
setLevel(level) setLevel(level)
} }
def levelDown() { def levelDown() {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float def step = state.stepsize as float
level-= step level-= step
if ( level < 1 ) if ( level < 1 )
level = 1 level = 1
setLevel(level) setLevel(level)
} }
def setLevel(value) { def setLevel(value) {
log.debug "in setLevel with value: ${value}" log.debug "in setLevel with value: ${value}"
def level = value as Integer def level = value as Integer
sendEvent( name: "level", value: level ) sendEvent( name: "level", value: level )
sendEvent( name: "switch.setLevel", value:level ) sendEvent( name: "switch.setLevel", value:level )
parent.setLevel( this, level ) parent.setLevel( this, level )
if (( level > 0 ) && ( level <= 100 )) if (( level > 0 ) && ( level <= 100 ))
on() on()
else else
off() off()
def levelSetting = level as float //def levelSetting = level as float
def bulbPowerMax = device.latestValue("setBulbPower") as float //def bulbPowerMax = device.latestValue("setBulbPower") as float
def calculatedPower = bulbPowerMax * (levelSetting / 100) //def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent(name: "power", value: calculatedPower.round(1)) //sendEvent(name: "power", value: calculatedPower.round(1))
} }
def poll() { def poll() {
@@ -200,29 +198,29 @@ def installed() {
} }
def updated() { def updated() {
initialize() initialize()
refresh() refresh()
} }
def initialize() { def initialize() {
if ( !settings.stepsize ) if ( !settings.stepsize )
state.stepsize = 10 //set the default stepsize state.stepsize = 10 //set the default stepsize
else else
state.stepsize = settings.stepsize state.stepsize = settings.stepsize
} }
/******************************************************************************* /*******************************************************************************
Method :uninstalled(args) Method :uninstalled(args)
(args) :none (args) :none
returns:Nothing returns:Nothing
ERRORS :No error handling is done ERRORS :No error handling is done
Purpose:This is standard ST method. Purpose:This is standard ST method.
Gets called when "remove" is selected in child device "preferences" Gets called when "remove" is selected in child device "preferences"
tile. It also get's called when "deleteChildDevice(child)" is tile. It also get's called when "deleteChildDevice(child)" is
called from parent service manager app. called from parent service manager app.
*******************************************************************************/ *******************************************************************************/
def uninstalled() { def uninstalled() {
log.debug "Executing 'uninstall' in device type" log.debug "Executing 'uninstall' in device type"
parent.uninstallFromChildDevice(this) parent.uninstallFromChildDevice(this)
} }

View File

@@ -24,24 +24,17 @@ definition(
iconX3Url: "http://www.gidjit.com/appicon@3x.png", iconX3Url: "http://www.gidjit.com/appicon@3x.png",
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"]) oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
preferences(oauthPage: "deviceAuthorization") {
// deviceAuthorization page is simply the devices to authorize
page(name: "deviceAuthorization", title: "Device Authorization", nextPage: "instructionPage",
install: false, uninstall: true) {
section ("Allow Gidjit to have access, thereby allowing you to quickly control and monitor your following devices. Privacy Policy can be found at http://priv.gidjit.com/privacy.html") {
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
}
}
page(name: "instructionPage", title: "Device Discovery", install: true) { preferences {
section() { section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
paragraph "Now the process is complete return to the Devices section of the Detected Screen. From there and you can add actions to each of your device panels, including launching SmartThings routines." input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
} input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
} input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
}
} }
mappings { mappings {
path("/structureinfo") { path("/structureinfo") {
action: [ action: [

View File

@@ -337,10 +337,10 @@ def initialize() {
settings.devices.each { settings.devices.each {
def deviceId = it def deviceId = it
def detail = state?.deviceDetail[deviceId] def detail = state.deviceDetail[deviceId]
try { try {
switch(detail?.type) { switch(detail.type) {
case 'NAMain': case 'NAMain':
log.debug "Base station" log.debug "Base station"
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name) createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
@@ -487,12 +487,12 @@ def poll() {
log.debug "State: ${state.deviceState}" log.debug "State: ${state.deviceState}"
settings.devices.each { deviceId -> settings.devices.each { deviceId ->
def detail = state?.deviceDetail[deviceId] def detail = state.deviceDetail[deviceId]
def data = state?.deviceState[deviceId] def data = state.deviceState[deviceId]
def child = children?.find { it.deviceNetworkId == deviceId } def child = children.find { it.deviceNetworkId == deviceId }
log.debug "Update: $child"; log.debug "Update: $child";
switch(detail?.type) { switch(detail.type) {
case 'NAMain': case 'NAMain':
log.debug "Updating NAMain $data" log.debug "Updating NAMain $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale()) child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())

View File

@@ -0,0 +1,679 @@
/**
* TCP Bulbs (Connect)
*
* Copyright 2014 Todd Wackford
*
* 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.
*
*/
import java.security.MessageDigest;
private apiUrl() { "https://tcp.greenwavereality.com/gwr/gop.php?" }
definition(
name: "TCP Bulbs - more reliable",
namespace: "mmacaula",
author: "Mike Macaulay",
description: "Connect your TCP bulbs to SmartThings using Cloud to Cloud integration. You must create a remote login acct on TCP Mobile App.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png",
singleInstance: true
)
preferences {
def msg = """Tap 'Next' after you have entered in your TCP Mobile remote credentials.
Once your credentials are accepted, SmartThings will scan your TCP installation for Bulbs."""
page(name: "selectDevices", title: "Connect Your TCP Lights to SmartThings", install: false, uninstall: true, nextPage: "chooseBulbs") {
section("TCP Connected Remote Credentials") {
input "username", "text", title: "Enter TCP Remote Email/UserName", required: true
input "password", "password", title: "Enter TCP Remote Password", required: true
paragraph msg
}
}
page(name: "chooseBulbs", title: "Choose Bulbs to Control With SmartThings", content: "initialize")
}
def installed() {
debugOut "Installed with settings: ${settings}"
unschedule()
unsubscribe()
setupBulbs()
log.debug "schedule every 5 minutes syncronizeDevices)"
runEvery5Minutes(syncronizeDevices)
}
def updated() {
debugOut "Updated with settings: ${settings}"
unschedule()
setupBulbs()
log.debug "schedule update every 5 minutes syncronizeDevices)"
runEvery5Minutes(syncronizeDevices)
}
def uninstalled()
{
unschedule() //in case we have hanging runIn()'s
}
private removeChildDevices(delete)
{
debugOut "deleting ${delete.size()} bulbs"
debugOut "deleting ${delete}"
delete.each {
deleteChildDevice(it.device.deviceNetworkId)
}
}
def uninstallFromChildDevice(childDevice)
{
def errorMsg = "uninstallFromChildDevice was called and "
if (!settings.selectedBulbs) {
debugOut errorMsg += "had empty list passed in"
return
}
def dni = childDevice.device.deviceNetworkId
if ( !dni ) {
debugOut errorMsg += "could not find dni of device"
return
}
def newDeviceList = settings.selectedBulbs - dni
app.updateSetting("selectedBulbs", newDeviceList)
debugOut errorMsg += "completed succesfully"
}
def setupBulbs() {
debugOut "In setupBulbs"
def bulbs = state.devices
def deviceFile = "TCP Bulb"
selectedBulbs.each { did ->
//see if this is a selected bulb and install it if not already
def d = getChildDevice(did)
if(!d) {
def newBulb = bulbs.find { (it.did) == did }
d = addChildDevice("wackford", deviceFile, did, null, [name: "${newBulb?.name}", label: "${newBulb?.name}", completedSetup: true])
/*if ( isRoom(did) ) { //change to the multi light group icon for a room device
d.setIcon("switch", "on", "st.lights.multi-light-bulb-on")
d.setIcon("switch", "off", "st.lights.multi-light-bulb-off")
d.save()
}*/
} else {
debugOut "We already added this device"
}
}
// Delete any that are no longer in settings
def delete = getChildDevices().findAll { !selectedBulbs?.contains(it.deviceNetworkId) }
removeChildDevices(delete)
//we want to ensure syncronization between rooms and bulbs
//syncronizeDevices()
}
def initialize() {
atomicState.token = ""
getToken()
if ( atomicState.token == "error" ) {
return dynamicPage(name:"chooseBulbs", title:"TCP Login Failed!\r\nTap 'Done' to try again", nextPage:"", install:false, uninstall: false) {
section("") {}
}
} else {
"we're good to go"
debugOut "We have Token."
}
//getGatewayData() //we really don't need anything from the gateway
deviceDiscovery()
def options = devicesDiscovered() ?: []
def msg = """Tap 'Done' after you have selected the desired devices."""
return dynamicPage(name:"chooseBulbs", title:"TCP and SmartThings Connected!", nextPage:"", install:true, uninstall: true) {
section("Tap Below to View Device List") {
input "selectedBulbs", "enum", required:false, title:"Select Bulb/Fixture", multiple:true, options:options
paragraph msg
}
}
}
def deviceDiscovery() {
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
def Params = [
cmd: "RoomGetCarousel",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(Params)
def rooms = ""
log.debug 'trying to discover devices'
apiPost(cmd) { response ->
rooms = response.data.gip.room
}
debugOut "rooms data = ${rooms}"
def devices = []
def bulbIndex = 1
def lastRoomName = null
def deviceList = []
if ( rooms[1] == null ) {
def roomId = rooms.rid
def roomName = rooms.name
devices = rooms.device
if ( devices[1] != null ) {
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
devices.each({
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
})
} else {
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
}
} else {
rooms.each({
devices = it.device
def roomName = it.name
if ( devices[1] != null ) {
def roomId = it?.rid
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
devices.each({
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
})
} else {
debugOut "Bulb Device Data: did:${devices?.did} room:${roomName} BulbName:${devices?.name}"
deviceList += ["name" : "${roomName} ${devices?.name}", "did" : "${devices?.did}", "type" : "bulb"]
}
})
}
devices = ["devices" : deviceList]
state.devices = devices.devices
}
Map devicesDiscovered() {
def devices = state.devices
def map = [:]
if (devices instanceof java.util.Map) {
devices.each {
def value = "${it?.name}"
def key = it?.did
map["${key}"] = value
}
} else { //backwards compatable
devices.each {
def value = "${it?.name}"
def key = it?.did
map["${key}"] = value
}
}
map
}
def getGatewayData() {
debugOut "In getGatewayData"
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
def qParams = [
cmd: "GatewayGetInfo",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "the gateway reponse is ${response.data.gip.gateway}"
}
}
def getToken(Closure callback) {
atomicState.token = ""
if (password) {
def hashedPassword = generateMD5(password)
def data = "<gip><version>1</version><email>${username}</email><password>${hashedPassword}</password></gip>"
def qParams = [
cmd : "GWRLogin",
data: "${data}",
fmt : "json"
]
def cmd = toQueryString(qParams)
apiPost(cmd) { response ->
def status = response.data.gip.rc
//sendNotificationEvent("Get token status ${status}")
if (status != "200") {//success code = 200
def errorText = response.data.gip.error
debugOut "Error logging into TCP Gateway. Error = ${errorText}"
atomicState.token = "error"
} else {
atomicState.token = response.data.gip.token
if(callback){
callback.call()
}
}
}
} else {
log.warn "Unable to log into TCP Gateway. Error = Password is null"
atomicState.token = "error"
}
}
def apiPost(String data, Integer retryCount = 0, Closure callback) {
debugOut "In apiPost with data: ${data}"
def params = [
uri: apiUrl(),
body: data
]
httpPost(params) {
response ->
def rc = response.data.gip.rc
if ( rc == "200" ) {
debugOut ("Return Code = ${rc} = Command Succeeded.")
callback.call(response)
} else if ( rc.startsWith("4") || rc.startsWith("5") ) {
debugOut "Return Code = ${rc} = Error: Something happened!" //Error code from gateway
sendNotificationEvent("Return Code = ${rc} = Error: Something happened! Retry # ${retryCount}" )
log.debug "Refreshing Token"
if(retryCount > 5){
// give up, send a notification
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Gave up after ${retryCount} tries")
}
getToken({ ->
def updatedTokenData = data.replaceFirst("<token>[^<]*</token>", '<token>${atomicState.token}</token>')
// try again if we got our token
sendNotificationEvent('re-fetched token, trying again')
apiPost(updatedTokenData, retryCount++, callback)
})
//callback.call(response) //stubbed out so getToken works (we had race issue)
} else {
log.error "Return Code = ${rc} = Error!" //Error code from gateway
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Check that TCP Gateway is online")
callback.call(response)
}
}
}
//this is not working. TCP power reporting is broken. Leave it here for future fix
def calculateCurrentPowerUse(deviceCapability, usePercentage) {
debugOut "In calculateCurrentPowerUse()"
debugOut "deviceCapability: ${deviceCapability}"
debugOut "usePercentage: ${usePercentage}"
def calcPower = usePercentage * 1000
def reportPower = calcPower.round(1) as String
debugOut "report power = ${reportPower}"
return reportPower
}
def generateSha256(String s) {
MessageDigest digest = MessageDigest.getInstance("SHA-256")
digest.update(s.bytes)
new BigInteger(1, digest.digest()).toString(16).padLeft(40, '0')
}
def generateMD5(String s) {
MessageDigest digest = MessageDigest.getInstance("MD5")
digest.update(s.bytes);
new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
}
String toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
def checkDevicesOnline(bulbs) {
debugOut "In checkDevicesOnline()"
def onlineBulbs = []
def thisBulb = []
bulbs.each {
def dni = it?.did
thisBulb = it
def data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
def qParams = [
cmd: "DeviceGetInfo",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(qParams)
def bulbData = []
apiPost(cmd) { response ->
bulbData = response.data.gip
}
if ( bulbData?.offline == "1" ) {
debugOut "${it?.name} is offline with offline value of ${bulbData?.offline}"
} else {
debugOut "${it?.name} is online with offline value of ${bulbData?.offline}"
onlineBulbs += thisBulb
}
}
return onlineBulbs
}
def syncronizeDevices() {
debugOut "In syncronizeDevices"
def update = getChildDevices().findAll { selectedBulbs?.contains(it.deviceNetworkId) }
update.each {
def dni = getChildDevice( it.deviceNetworkId )
debugOut "dni = ${dni}"
if (isRoom(dni)) {
pollRoom(dni)
} else {
poll(dni)
}
}
getToken()
}
boolean isRoom(dni) {
def device = state.devices.find() {(( it.type == 'room') && (it.did == "${dni}"))}
}
boolean isBulb(dni) {
def device = state.devices.find() {(( it.type == 'bulb') && (it.did == "${dni}"))}
}
def debugEvent(message, displayEvent) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
def debugOut(msg) {
log.debug msg
//sendNotificationEvent(msg) //Uncomment this for troubleshooting only
}
/**************************************************************************
Child Device Call In Methods
**************************************************************************/
def on(childDevice) {
debugOut "On request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>1</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>1</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "ON result: ${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
def off(childDevice) {
debugOut "Off request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>0</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>0</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
def setLevel(childDevice, value) {
debugOut "setLevel request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><type>level</type><value>${value}</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did><type>level</type><value>${value}</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
// Really not called from child, but called from poll() if it is a room
def pollRoom(dni) {
debugOut "In pollRoom"
def data = ""
def cmd = ""
def roomDeviceData = []
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><fields>name,power,control,status,state</fields></gip>"
cmd = "RoomGetDevices"
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
roomDeviceData = response.data.gip
}
debugOut "Room Data: ${roomDeviceData}"
def totalPower = 0
def totalLevel = 0
def cnt = 0
def onCnt = 0 //used to tally on/off states
roomDeviceData.device.each({
if ( getChildDevice(it.did) ) {
totalPower += it.other.bulbpower.toInteger()
totalLevel += it.level.toInteger()
onCnt += it.state.toInteger()
cnt += 1
}
})
def avgLevel = totalLevel/cnt
def usingPower = totalPower * (avgLevel / 100) as float
def room = getChildDevice( dni )
//the device is a room but we use same type file
sendEvent( dni, [name: "setBulbPower",value:"${totalPower}"] ) //used in child device calcs
//if all devices in room are on, room is on
if ( cnt == onCnt ) { // all devices are on
sendEvent( dni, [name: "switch",value:"on"] )
sendEvent( dni, [name: "power",value:usingPower.round(1)] )
} else { //if any device in room is off, room is off
sendEvent( dni, [name: "switch",value:"off"] )
sendEvent( dni, [name: "power",value:0.0] )
}
debugOut "Room Using Power: ${usingPower.round(1)}"
}
def poll(childDevice) {
debugOut "In poll() with ${childDevice}"
def dni = childDevice.device.deviceNetworkId
def bulbData = []
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
pollRoom(dni)
return
}
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
cmd = "DeviceGetInfo"
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
bulbData = response.data.gip
debugOut "This Bulbs Data Return = ${bulbData}"
def bulb = getChildDevice( dni )
//set the devices power max setting to do calcs within the device type
if ( bulbData.other.bulbpower )
sendEvent( dni, [name: "setBulbPower",value:"${bulbData.other.bulbpower}"] )
if (( bulbData.state == "1" ) && ( bulb?.currentValue("switch") != "on" ))
sendEvent( dni, [name: "switch",value:"on"] )
if (( bulbData.state == "0" ) && ( bulb?.currentValue("switch") != "off" ))
sendEvent( dni, [name: "switch",value:"off"] )
//if ( bulbData.level != bulb?.currentValue("level")) {
// sendEvent( dni, [name: "level",value: "${bulbData.level}"] )
// sendEvent( dni, [name: "setLevel",value: "${bulbData.level}"] )
//}
if (( bulbData.state == "1" ) && ( bulbData.other.bulbpower )) {
def levelSetting = bulbData.level as float
def bulbPowerMax = bulbData.other.bulbpower as float
def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent( dni, [name: "power", value: calculatedPower.round(1)] )
}
if (( bulbData.state == "0" ) && ( bulbData.other.bulbpower ))
sendEvent( dni, [name: "power", value: 0.0] )
}
}

View File

@@ -39,7 +39,6 @@ preferences {
page(name: "completionPage") page(name: "completionPage")
page(name: "numbersPage") page(name: "numbersPage")
page(name: "controllerExplanationPage") page(name: "controllerExplanationPage")
page(name: "unsupportedDevicesPage")
} }
def rootPage() { def rootPage() {
@@ -48,9 +47,6 @@ def rootPage() {
section("What to dim") { section("What to dim") {
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true) input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
if (dimmers) { if (dimmers) {
if (dimmersContainUnsupportedDevices()) {
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
}
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete") href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
} }
} }
@@ -75,31 +71,6 @@ def rootPage() {
} }
} }
def unsupportedDevicesPage() {
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
dynamicPage(name: "unsupportedDevicesPage") {
if (unsupportedDimmers) {
section("These devices do not support the setLevel command") {
unsupportedDimmers.each {
paragraph deviceLabel(it)
}
}
section {
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
}
section {
paragraph "If you think there is a mistake here, please contact support."
}
} else {
section {
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
}
}
}
}
def controllerExplanationPage() { def controllerExplanationPage() {
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") { dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
@@ -237,7 +208,7 @@ def completionPage() {
} }
section("Notifications") { section("Notifications") {
input("recipients", "contact", title: "Send notifications to", required: false) { input("recipients", "contact", title: "Send notifications to") {
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false) input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false) input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
} }
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
} else { } else {
def shouldChangeColors = (colorize && colorize != "false") def shouldChangeColors = (colorize && colorize != "false")
def canChangeColors = hasSetColorCommand(dimmer)
if (shouldChangeColors && hasSetColorCommand(dimmer)) { log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
def hue = getHue(dimmer, nextLevel)
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}" if (shouldChangeColors && canChangeColors) {
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel]) dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
} else if (hasSetLevelCommand(dimmer)) {
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
dimmer.setLevel(nextLevel)
} else { } else {
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands." dimmer.setLevel(nextLevel)
} }
} }
@@ -720,7 +689,7 @@ def completionPercentage() {
def now = new Date().getTime() def now = new Date().getTime()
def timeElapsed = now - atomicState.start def timeElapsed = now - atomicState.start
def totalRunTime = totalRunTimeMillis() ?: 1 def totalRunTime = totalRunTimeMillis()
def percentComplete = timeElapsed / totalRunTime * 100 def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentComplete: ${percentComplete}" log.debug "percentComplete: ${percentComplete}"
@@ -848,21 +817,24 @@ private getRedHue(level) {
if (level >= 96) return 17 if (level >= 96) return 17
} }
private dimmersContainUnsupportedDevices() {
def found = dimmers.find { hasSetLevelCommand(it) == false }
return found != null
}
private hasSetLevelCommand(device) { private hasSetLevelCommand(device) {
return hasCommand(device, "setLevel") def isDimmer = false
device.supportedCommands.each {
if (it.name.contains("setLevel")) {
isDimmer = true
}
}
return isDimmer
} }
private hasSetColorCommand(device) { private hasSetColorCommand(device) {
return hasCommand(device, "setColor") def hasColor = false
} device.supportedCommands.each {
if (it.name.contains("setColor")) {
private hasCommand(device, String command) { hasColor = true
return (device.supportedCommands.find { it.name == command } != null) }
}
return hasColor
} }
private dimmersWithSetColorCommand() { private dimmersWithSetColorCommand() {

View File

@@ -689,7 +689,7 @@ def parse(childDevice, description) {
log.warn "Parsing Body failed - trying again..." log.warn "Parsing Body failed - trying again..."
poll() poll()
} }
if (body instanceof java.util.Map) { if (body instanceof java.util.HashMap) {
//poll response //poll response
def bulbs = getChildDevices() def bulbs = getChildDevices()
for (bulb in body) { for (bulb in body) {

View File

@@ -658,73 +658,29 @@ def updateDevice() {
def data = request.JSON def data = request.JSON
def command = data.command def command = data.command
def arguments = data.arguments def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}" log.debug "updateDevice, params: ${params}, request: ${data}"
if (!command) { if (!command) {
render status: 400, data: '{"msg": "command is required"}' render status: 400, data: '{"msg": "command is required"}'
} else { } else {
def device = allDevices.find { it.id == params.id } def device = allDevices.find { it.id == params.id }
if (device) { if (device) {
if (validateCommand(device, command)) { if (device.hasCommand("$command")) {
if (arguments) { if (arguments) {
device."$command"(*arguments) device."$command"(*arguments)
} else { } else {
device."$command"() device."$command"()
} }
render status: 204, data: "{}" render status: 204, data: "{}"
} else { } else {
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}' render status: 404, data: '{"msg": "Command not supported by this Device"}'
} }
} else { } else {
render status: 404, data: '{"msg": "Device not found"}' render status: 404, data: '{"msg": "Device not found"}'
} }
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(device)
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
return (command in capabilityCommands[currentDeviceCapability] || (currentDeviceCapability == "Switch" && command == "setLevel" && device.hasCommand("setLevel"))) ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
} }
} }
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(device) {
def capName = ""
if (switches.find{it.id == device.id})
capName = "Switch"
else if (alarms.find{it.id == device.id})
capName = "Alarm"
else if (locks.find{it.id == device.id})
capName = "Lock"
log.trace "Device: $device - Capability Name: $capName"
return capName
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
def listSubscriptions() { def listSubscriptions() {
log.debug "listSubscriptions()" log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect { app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
@@ -823,33 +779,18 @@ def deviceHandler(evt) {
} }
def sendToHarmony(evt, String callbackUrl) { def sendToHarmony(evt, String callbackUrl) {
def callback = new URI(callbackUrl) def callback = new URI(callbackUrl)
if (callback.port != -1) { def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path sendHubCommand(new physicalgraph.device.HubAction(
sendHubCommand(new physicalgraph.device.HubAction( method: "POST",
method: "POST", path: path,
path: path, headers: [
headers: [ "Host": host,
"Host": host, "Content-Type": "application/json"
"Content-Type": "application/json" ],
], body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]] ))
))
} else {
def params = [
uri: callbackUrl,
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
]
try {
log.debug "Sending data to Harmony Cloud: $params"
httpPostJson(params) { resp ->
log.debug "Harmony Cloud - Response: ${resp.status}"
}
} catch (e) {
log.error "Harmony Cloud - Something went wrong: $e"
}
}
} }
def listHubs() { def listHubs() {

View File

@@ -92,86 +92,21 @@ void updateLock() {
private void updateAll(devices) { private void updateAll(devices) {
def command = request.JSON?.command def command = request.JSON?.command
def type = params.param1 if (command) {
if (!devices) { devices."$command"()
httpError(404, "Devices not found")
}
if (command){
devices.each { device ->
executeCommand(device, type, command)
}
} }
} }
private void update(devices) { private void update(devices) {
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id" log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
def command = request.JSON?.command def command = request.JSON?.command
def type = params.param1
def device = devices?.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
}
if (command) { if (command) {
executeCommand(device, type, command) def device = devices.find { it.id == params.id }
} if (!device) {
} httpError(404, "Device not found")
} else {
/** device."$command"()
* Validating the command passed by the user based on capability. }
* @return boolean
*/
def validateCommand(device, deviceType, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(deviceType)
if (capabilityCommands[currentDeviceCapability]) {
return command in capabilityCommands[currentDeviceCapability] ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
}
}
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(type) {
switch(type) {
case "switches":
return "Switch"
case "locks":
return "Lock"
default:
return type
}
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
/**
* Validates and executes the command
* on the device or devices
*/
def executeCommand(device, type, command) {
if (validateCommand(device, type, command)) {
device."$command"()
} else {
httpError(403, "Access denied. This command is not supported by current capability.")
} }
} }