Compare commits

...

11 Commits

Author SHA1 Message Date
Vinay Rao
e1b1479cfc Merge pull request #1101 from CosmicPuppy/CosmicPuppy-SensorActuator-Patches
Added Capability "Sensor" and/or "Actuator" per DTH standards.
2016-08-11 12:12:34 -07:00
Zach Varberg
16e4954f10 Merge pull request #1087 from varzac/update-zonestatus
Update DTHs to use ZigBee library ZoneStatus
2016-08-10 10:04:59 -05:00
Vinay Rao
929f8e1a44 Merge pull request #1110 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-08-09 14:54:50 -07:00
SmartThings, Inc
ad50582e92 Merge pull request #1013 from SmartThingsCommunity/MSA-1372-2
Merged publication request 'Gidjit - Smart Launcher OAuth Permission Capability'
2016-08-09 14:02:26 -05:00
Vinay Rao
6de6704502 Merge pull request #694 from workingmonk/feature/zigbee_valve
[DEVC-21] zigbee valve DTH for HA 1.2 Waxman Valve
2016-08-08 20:05:45 -07:00
Vinay Rao
a5bc475cc1 zigbee valve DTH for HA 1.2 Waxman Valve 2016-08-08 20:04:23 -07:00
CosmicPuppy
7beb2e3905 Added Capability "Sensor" and/or "Actuator" per http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html?highlight=sensor%20actuator#actuator-and-sensor.
There are some integrations out there using the "Actuator" and "Sensor" Capabilities and this doesn't show up for them (e.g., SmartTiles V6).
2016-08-05 01:09:32 -07:00
Vinay Rao
c7396349f1 Merge pull request #851 from workingmonk/feature/new_button_dth
DVCSMP-1742 DVCSMP-1513 Adding support for osram and iris ZigBee buttons
2016-08-04 15:55:47 -07:00
Vinay Rao
089cc1a5dd adding support for osram and iris buttons
commenting out iris fingerprints until cert is complete
2016-08-04 15:43:22 -07:00
Zach Varberg
7bfa0304af Update DTHs to use ZigBee library ZoneStatus
This makes use of a new class exposed from the ZigBee library to
automatically parse the ZoneStatus attribute bit map and expose
the individual values in a simple way.  This fixes a lot of
unhandled cases in the previous copy-pasted switch statements,
as well as allowing for a simpler interface with less copy/pasted
code.
2016-08-03 15:53:30 -05:00
Matthew Page
ef29820fa1 MSA-1372: This is an updated version of the previous, however a user can now set the preferences and permissions from an OAuth page. 2016-06-24 10:31:30 -05:00
23 changed files with 406 additions and 559 deletions

View File

@@ -13,6 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -143,51 +144,14 @@ private Map parseReportAttributeMessage(String description) {
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0030': // Closed/No Motion/Dry
log.debug 'no motion'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:]
case '0x0032': // Open/Motion/Wet
log.debug 'motion'
resultMap.name = 'motion'
resultMap.value = 'active'
break
result.name = 'motion'
result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
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
return resultMap
}
def refresh()

View File

@@ -13,7 +13,10 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery"
@@ -219,40 +222,33 @@ private Map parseReportAttributeMessage(String description) {
}
private List parseIasMessage(String description) {
List parsedMsg = description.split(" ")
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
log.debug "parseIasMessage: $description"
List resultListMap = []
Map resultMap_battery = [:]
Map resultMap_battery_state = [:]
Map resultMap_sensor = [:]
// Relevant bit field definitions from ZigBee spec
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}"
resultMap_sensor.name = "contact"
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
// Check each relevant bit, create map for it, and add to list
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
log.debug "parseIasMessage: Battery Status ${zs.battery}"
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
/* 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
resultMap_battery_state.name = "battery_state"
if (zoneStatus & TROUBLE_BIT) {
if (zs.isTroubleSet()) {
resultMap_battery_state.value = "failed"
resultMap_battery.name = "battery"
resultMap_battery.value = 0
}
else {
if (zoneStatus & BATTERY_BIT) {
if (zs.isBatterySet()) {
resultMap_battery_state.value = "low"
// to generate low battery notification by the platform
@@ -270,9 +266,6 @@ private List parseIasMessage(String description) {
}
*/
resultMap_sensor.name = "contact"
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
resultListMap << resultMap_battery_state
resultListMap << resultMap_battery
resultListMap << resultMap_sensor

View File

@@ -13,6 +13,8 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -22,6 +24,7 @@ metadata {
capability "Temperature Measurement"
capability "Water Sensor"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
@@ -169,42 +172,9 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
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
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
}
def getTemperature(value) {

View File

@@ -13,6 +13,8 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -22,6 +24,7 @@ metadata {
capability "Temperature Measurement"
capability "Refresh"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
@@ -182,44 +185,10 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
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
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
}
def getTemperature(value) {

View File

@@ -15,6 +15,7 @@
*/
//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 {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -168,44 +169,8 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
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
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
}
def getTemperature(value) {

View File

@@ -13,6 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -224,47 +225,13 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
case '0x0021': // Open/Motion/Wet
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
if(garageSensor != "Yes") {
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
return resultMap
}

View File

@@ -14,6 +14,7 @@
*
*/
//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 {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -24,6 +25,7 @@
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
}
@@ -171,40 +173,9 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
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
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
def getTemperature(value) {

View File

@@ -13,6 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -167,40 +168,8 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
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
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
def getTemperature(value) {

View File

@@ -21,6 +21,7 @@ metadata {
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Health Check"
capability "Sensor"
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -161,40 +162,9 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
ZoneStatus zs = zigbee.parseZoneStatus(description)
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
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
def getTemperature(value) {

View File

@@ -1,8 +1,7 @@
/**
* Iris Smart Fob
* ZigBee Button
*
* 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
* in compliance with the License. You may obtain a copy of the License at:
@@ -14,181 +13,235 @@
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Battery"
capability "Button"
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Actuator"
capability "Battery"
capability "Button"
capability "Configuration"
capability "Presence Sensor"
capability "Sensor"
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
}
preferences{
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
defaultValue: 3, displayDuringSetup: false)
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
capability "Refresh"
capability "Sensor"
command "enrollResponse"
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
}
tiles(scale: 2) {
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
simulator {}
main (["presence"])
details(["presence","button","battery"])
}
preferences {
section {
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 1, displayDuringSetup: false)
}
}
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", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
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 descMap = zigbee.parseDescriptionAsMap(description)
logIt descMap
state.lastCheckin = now()
logIt "lastCheckin = ${state.lastCheckin}"
handlePresenceEvent(true)
def results = []
if (description?.startsWith('catchall:'))
results = parseCatchAllMessage(descMap)
else if (description?.startsWith('read attr -'))
results = parseReportAttributeMessage(descMap)
else logIt(descMap, "trace")
return results;
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
sendEvent(event)
}
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 result = event ? createEvent(event) : []
if (description?.startsWith('enroll request')) {
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() {
startTimer()
configure()
initialize()
}
def configure(){
logIt "Configuring Smart Fob..."
[
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
] +
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
}
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 initialize() {
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2)
}
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)
else if ((device.getDataValue("manufacturer") == "CentraLite") &&
((device.getDataValue("model") == "3455-L") || (device.getDataValue("model") == "3460-L"))) {
sendEvent(name: "numberOfButtons", value: 1)
}
else if ((device.getDataValue("manufacturer") == "CentraLite") && (device.getDataValue("model") == "3450-L")) {
sendEvent(name: "numberOfButtons", value: 4)
}
else {
//default. can be changed
sendEvent(name: "numberOfButtons", value: 4)
}
}
// ****** Utility functions ******
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 SmartThings
* Copyright 2016 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:
@@ -11,100 +11,133 @@
* for the specific language governing permissions and limitations under the License.
*
*/
/*
* Capabilities
* - Battery
* - Configuration
* - Refresh
* - Switch
* - Valve
*/
metadata {
definition (name: "Zigbee Valve", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
capability "Configuration"
capability "Refresh"
capability "Switch"
capability "Valve"
definition (name: "ZigBee Valve", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Battery"
capability "Configuration"
capability "Power Source"
capability "Refresh"
capability "Valve"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0020,0006,0B02", outClusters: "0003"
}
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, 0006, 0008, 000F, 0020, 0B02", outClusters: "0003, 0019", manufacturer: "WAXMAN", model: "House Water Valve - MDL-TBD", deviceJoinName: "Waxman House Water Valve"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: 'closed', action: "switch.on", icon: "st.Outdoor.outdoor16", backgroundColor: "#e86d13"
state "on", label: 'open', action: "switch.off", icon: "st.Outdoor.outdoor16", backgroundColor: "#53a7c0"
}
main "switch"
details(["switch"])
}
tiles(scale: 2) {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening"
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"
}
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
def parse(String description) {
log.info description
if (description?.startsWith("catchall:")) {
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
def msg = zigbee.parse(description)
log.debug "Parse returned ${result?.descriptionText}"
return result
log.trace msg
log.trace "data: $msg.data"
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
// Commands to device
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
if(event.name == "switch") {
event.name = "contact" //0006 cluster in valve is tied to contact
if(event.value == "on") {
event.value = "open"
}
else if(event.value == "off") {
event.value = "closed"
}
}
sendEvent(event)
}
else {
def descMap = zigbee.parseDescriptionAsMap(description)
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")
}
else if (value == "03") {
sendEvent(name: "powerSource", value: "Battery")
}
else if (value == "04") {
sendEvent(name: "powerSource", value: "DC")
}
else {
sendEvent(name: "powerSource", value: "Unknown")
}
}
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() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
zigbee.on()
}
def close() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
zigbee.off()
}
def refresh() {
log.debug "sending refresh command"
"st rattr 0x${device.deviceNetworkId} 1 6 0"
log.debug "refresh called"
zigbee.onOffRefresh() +
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() {
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}"
log.debug "Configuring Reporting and Bindings."
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) +
zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
}

View File

@@ -24,17 +24,24 @@ definition(
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
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
}
preferences {
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
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
}
}
page(name: "instructionPage", title: "Device Discovery", install: true) {
section() {
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."
}
}
}
mappings {
path("/structureinfo") {
action: [