mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
Compare commits
25 Commits
PROD_2017.
...
MSA-1994-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55db0ac12a | ||
|
|
01c2968f91 | ||
|
|
40b75ce665 | ||
|
|
9b01a7d8be | ||
|
|
12bb6c0492 | ||
|
|
7cf8bb1917 | ||
|
|
d4fd778a64 | ||
|
|
eb870e5f31 | ||
|
|
d60657e466 | ||
|
|
d8dc70ae9e | ||
|
|
a99e050c6b | ||
|
|
5bf7caca0d | ||
|
|
3457bbad42 | ||
|
|
c6c4b09fbb | ||
|
|
3eddd68532 | ||
|
|
8d79379bba | ||
|
|
7e25d32c70 | ||
|
|
dd4da29bcd | ||
|
|
b6136bf1d5 | ||
|
|
68f5cda945 | ||
|
|
da42ee63fb | ||
|
|
c58132a69e | ||
|
|
f069ea3087 | ||
|
|
1d629cfc9a | ||
|
|
5b7a7097b8 |
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Utilitech Glass Break Sensor
|
||||
*
|
||||
* Author: Adam Heinmiller
|
||||
*
|
||||
* Date: 2014-11-09
|
||||
*/
|
||||
|
||||
metadata
|
||||
{
|
||||
definition (namespace: "adamheinmiller", name: "Utilitech Glass Break Sensor", author: "Adam Heinmiller")
|
||||
{
|
||||
capability "Contact Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint deviceId:"0xA102", inClusters:"0x20, 0x9C, 0x80, 0x82, 0x84, 0x87, 0x85, 0x72, 0x86, 0x5A"
|
||||
}
|
||||
|
||||
simulator
|
||||
{
|
||||
status "Activate Sensor": "command: 9C02, payload: 26 00 FF 00 00"
|
||||
status "Reset Sensor": "command: 9C02, payload: 26 00 00 00 00"
|
||||
|
||||
status "Battery Status 25%": "command: 8003, payload: 19"
|
||||
status "Battery Status 50%": "command: 8003, payload: 32"
|
||||
status "Battery Status 75%": "command: 8003, payload: 4B"
|
||||
status "Battery Status 100%": "command: 8003, payload: 64"
|
||||
}
|
||||
|
||||
tiles
|
||||
{
|
||||
standardTile("contact", "device.contact", width: 2, height: 2)
|
||||
{
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#FF0000"
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat")
|
||||
{
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "contact"
|
||||
details(["contact", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
|
||||
updated()
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
def getTimestamp()
|
||||
{
|
||||
return new Date().time
|
||||
}
|
||||
|
||||
def getBatteryLevel(int pNewLevel)
|
||||
{
|
||||
def bl = state.BatteryLevel ?: [pNewLevel, pNewLevel, pNewLevel] as int[]
|
||||
|
||||
def iAvg = 4 + ((int)(pNewLevel + bl[0] + bl[1] + bl[2]) / 4)
|
||||
|
||||
state.BatteryLevel = [pNewLevel, bl[0], bl[1]]
|
||||
|
||||
//log.debug "New Bat Level: ${iAvg - (iAvg % 5)}, $state.BatteryLevel"
|
||||
|
||||
return iAvg - (iAvg % 5)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def parse(String description)
|
||||
{
|
||||
def result = []
|
||||
|
||||
// "0x20, 0x9C, 0x80, 0x82, 0x84, 0x87, 0x85, 0x72, 0x86, 0x5A"
|
||||
|
||||
def cmd = zwave.parse(description)
|
||||
|
||||
|
||||
//log.debug "Parse: Desc: $description, CMD: $cmd"
|
||||
|
||||
if (cmd)
|
||||
{
|
||||
result << zwaveEvent(cmd)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
|
||||
result << response(zwave.wakeUpV2.wakeUpNoMoreInformation())
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = [name: "battery", unit: "%", value: getBatteryLevel(cmd.batteryLevel)]
|
||||
|
||||
return createEvent(result)
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
|
||||
def result = [name: "contact"]
|
||||
|
||||
if (cmd.sensorState == 0)
|
||||
{
|
||||
result += [value: "closed", descriptionText: "${device.displayName} has reset"]
|
||||
}
|
||||
else if (cmd.sensorState == 255)
|
||||
{
|
||||
result += [value: "open", descriptionText: "${device.displayName} detected broken glass"]
|
||||
}
|
||||
|
||||
|
||||
return createEvent(result)
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||
{
|
||||
logCommand("**Unhandled**: $cmd")
|
||||
|
||||
return createEvent([descriptionText: "Unhandled: ${device.displayName}: ${cmd}", displayed: false])
|
||||
}
|
||||
|
||||
|
||||
def logCommand(cmd)
|
||||
{
|
||||
log.debug "Device Command: $cmd"
|
||||
}
|
||||
@@ -38,9 +38,9 @@ metadata {
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL", icon: "st.Lighting.light18") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#00A0DC"
|
||||
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
@@ -52,20 +52,20 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#00A0DC"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#cccccc"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state "temperature", label:'${currentValue}°', unit:"F", icon:"", // would be better if the units would switch to the desired units of the system (imperial or metric)
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#1010ff"], // blue=cold
|
||||
[value: 65, color: "#a0a0f0"],
|
||||
[value: 70, color: "#e0e050"],
|
||||
[value: 75, color: "#f0d030"], // yellow
|
||||
[value: 80, color: "#fbf020"],
|
||||
[value: 85, color: "#fbdc01"],
|
||||
[value: 90, color: "#fb3a01"],
|
||||
[value: 95, color: "#fb0801"] // red=hot
|
||||
[value: 0, color: "#153591"], // blue=cold
|
||||
[value: 65, color: "#44b621"], // green
|
||||
[value: 70, color: "#44b621"], // green
|
||||
[value: 75, color: "#f1d801"], // yellow
|
||||
[value: 80, color: "#f1d801"], // yellow
|
||||
[value: 85, color: "#f1d801"], // yellow
|
||||
[value: 90, color: "#d04e00"], // red
|
||||
[value: 95, color: "#bc2323"] // red=hot
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
1545
devicetypes/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
1545
devicetypes/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,8 +34,8 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#cccccc")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#00A0DC")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
|
||||
@@ -95,12 +95,12 @@ metadata {
|
||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
||||
{
|
||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC" , nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
@@ -135,13 +135,13 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
@@ -189,13 +189,13 @@ metadata {
|
||||
|
||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,618 @@
|
||||
/**
|
||||
* Centralite Keypad
|
||||
*
|
||||
* Copyright 2015-2016 Mitch Pond, Zack Cornelius
|
||||
*
|
||||
* 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: "Centralite Keypad", namespace: "mitchpond", author: "Mitch Pond") {
|
||||
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Lock Codes"
|
||||
capability "Tamper Alert"
|
||||
capability "Tone"
|
||||
capability "button"
|
||||
capability "polling"
|
||||
capability "Contact Sensor"
|
||||
|
||||
attribute "armMode", "String"
|
||||
attribute "lastUpdate", "String"
|
||||
|
||||
command "setDisarmed"
|
||||
command "setArmedAway"
|
||||
command "setArmedStay"
|
||||
command "setArmedNight"
|
||||
command "setExitDelay", ['number']
|
||||
command "setEntryDelay", ['number']
|
||||
command "testCmd"
|
||||
command "sendInvalidKeycodeResponse"
|
||||
command "acknowledgeArmRequest"
|
||||
|
||||
fingerprint endpointId: "01", profileId: "0104", deviceId: "0401", inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019,0501", manufacturer: "CentraLite", model: "3400", deviceJoinName: "Xfinity 3400-X Keypad"
|
||||
fingerprint endpointId: "01", profileId: "0104", deviceId: "0401", inClusters: "0000,0001,0003,0020,0402,0500,0501,0B05,FC04", outClusters: "0019,0501", manufacturer: "CentraLite", model: "3405-L", deviceJoinName: "Iris 3405-L Keypad"
|
||||
}
|
||||
|
||||
preferences{
|
||||
input ("tempOffset", "number", title: "Enter an offset to adjust the reported temperature",
|
||||
defaultValue: 0, displayDuringSetup: false)
|
||||
input ("beepLength", "number", title: "Enter length of beep in seconds",
|
||||
defaultValue: 1, displayDuringSetup: false)
|
||||
|
||||
input ("motionTime", "number", title: "Time in seconds for Motion to become Inactive (Default:10, 0=disabled)", defaultValue: 10, displayDuringSetup: false)
|
||||
}
|
||||
|
||||
tiles (scale: 2) {
|
||||
multiAttributeTile(name: "keypad", type:"generic", width:6, height:4, canChangeIcon: true) {
|
||||
tileAttribute ("device.armMode", key: "PRIMARY_CONTROL") {
|
||||
attributeState("disarmed", label:'${currentValue}', icon:"st.Home.home2", backgroundColor:"#44b621")
|
||||
attributeState("armedStay", label:'ARMED/STAY', icon:"st.Home.home3", backgroundColor:"#ffa81e")
|
||||
attributeState("armedAway", label:'ARMED/AWAY', icon:"st.nest.nest-away", backgroundColor:"#d04e00")
|
||||
}
|
||||
tileAttribute("device.lastUpdate", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'Updated: ${currentValue}')
|
||||
}
|
||||
/*
|
||||
tileAttribute("device.battery", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'Battery: ${currentValue}%', unit:"%")
|
||||
}
|
||||
tileAttribute("device.battery", key: "VALUE_CONTROL") {
|
||||
attributeState "VALUE_UP", action: "refresh"
|
||||
attributeState "VALUE_DOWN", action: "refresh"
|
||||
}
|
||||
*/
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState "VALUE_UP", action: "refresh"
|
||||
attributeState "VALUE_DOWN", action: "refresh"
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
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"]
|
||||
]
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", decoration: "flat", canChangeBackground: true, width: 2, 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"
|
||||
}
|
||||
standardTile("tamper", "device.tamper", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "clear", label: 'Tamper', icon:"st.motion.acceleration.inactive", backgroundColor: "#ffffff"
|
||||
state "detected", label: 'Tamper', icon:"st.motion.acceleration.active", backgroundColor:"#cc5c5c"
|
||||
}
|
||||
standardTile("Panic", "device.contact", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "open", label: 'Panic', icon:"st.security.alarm.alarm", backgroundColor: "#ffffff"
|
||||
state "closed", label: 'Panic', icon:"st.security.alarm.clear", backgroundColor:"#bc2323"
|
||||
}
|
||||
|
||||
standardTile("Mode", "device.armMode", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "disarmed", label:'OFF', icon:"st.Home.home2", backgroundColor:"#44b621"
|
||||
state "armedStay", label:'OFF', icon:"st.Home.home3", backgroundColor:"#ffffff"
|
||||
state "armedAway", label:'OFF', icon:"st.net.nest-away", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
standardTile("beep", "device.beep", decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
|
||||
}
|
||||
valueTile("battery", "device.battery", 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", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
valueTile("armMode", "device.armMode", decoration: "flat", width: 2, height: 2) {
|
||||
state "armMode", label: '${currentValue}'
|
||||
}
|
||||
|
||||
main (["keypad"])
|
||||
details (["keypad","motion","tamper","Panic","Mode","beep","refresh","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'";
|
||||
def results = [];
|
||||
|
||||
//------Miscellaneous Zigbee message------//
|
||||
if (description?.startsWith('catchall:')) {
|
||||
|
||||
//log.debug zigbee.parse(description);
|
||||
|
||||
def message = zigbee.parse(description);
|
||||
|
||||
//------Profile-wide command (rattr responses, errors, etc.)------//
|
||||
if (message?.isClusterSpecific == false) {
|
||||
//------Default response------//
|
||||
if (message?.command == 0x0B) {
|
||||
if (message?.data[1] == 0x81)
|
||||
log.error "Device: unrecognized command: "+description;
|
||||
else if (message?.data[1] == 0x80)
|
||||
log.error "Device: malformed command: "+description;
|
||||
}
|
||||
//------Read attributes responses------//
|
||||
else if (message?.command == 0x01) {
|
||||
if (message?.clusterId == 0x0402) {
|
||||
log.debug "Device: read attribute response: "+description;
|
||||
|
||||
results = parseTempAttributeMsg(message)
|
||||
}}
|
||||
else
|
||||
log.debug "Unhandled profile-wide command: "+description;
|
||||
}
|
||||
//------Cluster specific commands------//
|
||||
else if (message?.isClusterSpecific) {
|
||||
//------IAS ACE------//
|
||||
if (message?.clusterId == 0x0501) {
|
||||
if (message?.command == 0x07) {
|
||||
motionON()
|
||||
}
|
||||
else if (message?.command == 0x04) {
|
||||
results = createEvent(name: "button", value: "pushed", data: [buttonNumber: 1], descriptionText: "$device.displayName panic button was pushed", isStateChange: true)
|
||||
panicContact()
|
||||
}
|
||||
else if (message?.command == 0x00) {
|
||||
results = handleArmRequest(message)
|
||||
log.trace results
|
||||
}
|
||||
}
|
||||
else log.debug "Unhandled cluster-specific command: "+description
|
||||
}
|
||||
}
|
||||
//------IAS Zone Enroll request------//
|
||||
else if (description?.startsWith('enroll request')) {
|
||||
log.debug "Sending IAS enroll response..."
|
||||
results = zigbee.enrollResponse()
|
||||
}
|
||||
//------Read Attribute response------//
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
results = parseReportAttributeMessage(description)
|
||||
}
|
||||
//------Temperature Report------//
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
log.debug "Got ST-style temperature report.."
|
||||
results = createEvent(getTemperatureResult(zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())))
|
||||
log.debug results
|
||||
}
|
||||
else if (description?.startsWith('zone status ')) {
|
||||
results = parseIasMessage(description)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
def configure() {
|
||||
log.debug "--- Configure Called"
|
||||
String hubZigbeeId = swapEndianHex(device.hub.zigbeeEui)
|
||||
def cmd = [
|
||||
//------IAS Zone/CIE setup------//
|
||||
"zcl global write 0x500 0x10 0xf0 {${hubZigbeeId}}", "delay 100",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200",
|
||||
|
||||
//------Set up binding------//
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x500 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x501 {${device.zigbeeId}} {}", "delay 200",
|
||||
|
||||
] +
|
||||
zigbee.configureReporting(1,0x20,0x20,3600,43200,0x01) +
|
||||
zigbee.configureReporting(0x0402,0x00,0x29,30,3600,0x0064)
|
||||
|
||||
return cmd + refresh()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
return sendStatusToDevice() +
|
||||
zigbee.readAttribute(0x0001,0x20) +
|
||||
zigbee.readAttribute(0x0402,0x00)
|
||||
}
|
||||
|
||||
private formatLocalTime(time, format = "EEE, MMM d yyyy @ h:mm a z") {
|
||||
if (time instanceof Long) {
|
||||
time = new Date(time)
|
||||
}
|
||||
if (time instanceof String) {
|
||||
//get UTC time
|
||||
time = timeToday(time, location.timeZone)
|
||||
}
|
||||
if (!(time instanceof Date)) {
|
||||
return null
|
||||
}
|
||||
def formatter = new java.text.SimpleDateFormat(format)
|
||||
formatter.setTimeZone(location.timeZone)
|
||||
return formatter.format(time)
|
||||
}
|
||||
|
||||
private parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
//log.debug "Desc Map: $descMap"
|
||||
|
||||
def results = []
|
||||
|
||||
if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
log.debug "Received battery level report"
|
||||
results = createEvent(getBatteryResult(Integer.parseInt(descMap.value, 16)))
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0034")
|
||||
{
|
||||
log.debug "Received Battery Rated Voltage: ${descMap.value}"
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0036")
|
||||
{
|
||||
log.debug "Received Battery Alarm Voltage: ${descMap.value}"
|
||||
}
|
||||
else if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
results = createEvent(getTemperatureResult(value))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private parseTempAttributeMsg(message) {
|
||||
byte[] temp = message.data[-2..-1].reverse()
|
||||
createEvent(getTemperatureResult(getTemperature(temp.encodeHex() as String)))
|
||||
}
|
||||
|
||||
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
|
||||
case '0x0000':
|
||||
resultMap = createEvent(name: "tamper", value: "clear", isStateChange: true, displayed: false)
|
||||
break
|
||||
case '0x0004':
|
||||
resultMap = createEvent(name: "tamper", value: "detected", isStateChange: true, displayed: false)
|
||||
break;
|
||||
default:
|
||||
log.debug "Invalid message code in IAS message: ${msgCode}"
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
String linkText = getLinkText(device)
|
||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
def motionON() {
|
||||
log.debug "--- Motion Detected"
|
||||
sendEvent(name: "motion", value: "active", displayed:true, isStateChange: true)
|
||||
|
||||
//-- Calculate Inactive timeout value
|
||||
def motionTimeRun = (settings.motionTime?:0).toInteger()
|
||||
|
||||
//-- If Inactive timeout was configured
|
||||
if (motionTimeRun > 0) {
|
||||
log.debug "--- Will become inactive in $motionTimeRun seconds"
|
||||
runIn(motionTimeRun, "motionOFF")
|
||||
}
|
||||
}
|
||||
|
||||
def motionOFF() {
|
||||
log.debug "--- Motion Inactive (OFF)"
|
||||
sendEvent(name: "motion", value: "inactive", displayed:true, isStateChange: true)
|
||||
}
|
||||
|
||||
def panicContact() {
|
||||
log.debug "--- Panic button hit"
|
||||
sendEvent(name: "contact", value: "open", displayed: true, isStateChange: true)
|
||||
runIn(3, "panicContactClose")
|
||||
}
|
||||
|
||||
def panicContactClose()
|
||||
{
|
||||
sendEvent(name: "contact", value: "closed", displayed: true, isStateChange: true)
|
||||
}
|
||||
|
||||
//TODO: find actual good battery voltage range and update this method with proper values for min/max
|
||||
//
|
||||
//Converts the battery level response into a percentage to display in ST
|
||||
//and creates appropriate message for given level
|
||||
|
||||
private getBatteryResult(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [name: 'battery']
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.5
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private getTemperature(value) {
|
||||
def celcius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celcius
|
||||
} else {
|
||||
return celsiusToFahrenheit(celcius) as Integer
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
//------Command handlers------//
|
||||
private handleArmRequest(message){
|
||||
def keycode = new String(message.data[2..-2] as byte[],'UTF-8')
|
||||
def reqArmMode = message.data[0]
|
||||
//state.lastKeycode = keycode
|
||||
log.debug "Received arm command with keycode/armMode: ${keycode}/${reqArmMode}"
|
||||
|
||||
//Acknowledge the command. This may not be *technically* correct, but it works
|
||||
/*List cmds = [
|
||||
"raw 0x501 {09 01 00 0${reqArmMode}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) } + createCodeEntryEvent(keycode, reqArmMode)
|
||||
*/
|
||||
def results = createCodeEntryEvent(keycode, reqArmMode)
|
||||
log.trace "Method: handleArmRequest(message): "+results
|
||||
return results
|
||||
}
|
||||
|
||||
def createCodeEntryEvent(keycode, armMode) {
|
||||
createEvent(name: "codeEntered", value: keycode as String, data: armMode as String,
|
||||
isStateChange: true, displayed: false)
|
||||
}
|
||||
|
||||
//
|
||||
//The keypad seems to be expecting responses that are not in-line with the HA 1.2 spec. Maybe HA 1.3 or Zigbee 3.0??
|
||||
//
|
||||
private sendStatusToDevice() {
|
||||
log.debug 'Sending status to device...'
|
||||
def armMode = device.currentValue("armMode")
|
||||
log.trace 'Arm mode: '+armMode
|
||||
def status = ''
|
||||
if (armMode == null || armMode == 'disarmed') status = 0
|
||||
else if (armMode == 'armedAway') status = 3
|
||||
else if (armMode == 'armedStay') status = 1
|
||||
else if (armMode == 'armedNight') status = 2
|
||||
|
||||
// If we're not in one of the 4 basic modes, don't update the status, don't want to override beep timings, exit delay is dependent on it being correct
|
||||
if (status != '')
|
||||
{
|
||||
return sendRawStatus(status)
|
||||
}
|
||||
else
|
||||
{
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Statuses:
|
||||
// 00 - Disarmed
|
||||
// 01 - Armed partial
|
||||
// 02 - Armed partial
|
||||
// 03 - Armed Away
|
||||
// 04 - ?
|
||||
// 05 - Fast beep (1 per second)
|
||||
// 05 - Entry delay (Uses seconds) Appears to keep the status lights as it was
|
||||
// 06 - Amber status blink (Ignores seconds)
|
||||
// 07 - ?
|
||||
// 08 - Red status blink
|
||||
// 09 - ?
|
||||
// 10 - Exit delay Slow beep (2 per second, accelerating to 1 beep per second for the last 10 seconds) - With red flashing status - Uses seconds
|
||||
// 11 - ?
|
||||
// 12 - ?
|
||||
// 13 - ?
|
||||
|
||||
private sendRawStatus(status, seconds = 00) {
|
||||
log.debug "Sending Status ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)} to device..."
|
||||
|
||||
// Seems to require frame control 9, which indicates a "Server to client" cluster specific command (which seems backward? I thought the keypad was the server)
|
||||
List cmds = ["raw 0x501 {09 01 04 ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)}}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 100']
|
||||
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) };
|
||||
return results
|
||||
}
|
||||
|
||||
def notifyPanelStatusChanged(status) {
|
||||
//TODO: not yet implemented. May not be needed.
|
||||
}
|
||||
//------------------------//
|
||||
|
||||
def setDisarmed() { setModeHelper("disarmed",0) }
|
||||
def setArmedAway(def delay=0) { setModeHelper("armedAway",delay) }
|
||||
def setArmedStay(def delay=0) { setModeHelper("armedStay",delay) }
|
||||
def setArmedNight(def delay=0) { setModeHelper("armedNight",delay) }
|
||||
|
||||
def setEntryDelay(delay) {
|
||||
setModeHelper("entryDelay", delay)
|
||||
sendRawStatus(5, delay) // Entry delay beeps
|
||||
}
|
||||
|
||||
def setExitDelay(delay) {
|
||||
setModeHelper("exitDelay", delay)
|
||||
sendRawStatus(10, delay) // Exit delay
|
||||
}
|
||||
|
||||
private setModeHelper(String armMode, delay) {
|
||||
sendEvent([name: "armMode", value: armMode, data: [delay: delay as int], isStateChange: true])
|
||||
def lastUpdate = formatLocalTime(now())
|
||||
sendEvent(name: "lastUpdate", value: lastUpdate, displayed: false)
|
||||
sendStatusToDevice()
|
||||
}
|
||||
|
||||
private setKeypadArmMode(armMode){
|
||||
Map mode = [disarmed: '00', armedAway: '03', armedStay: '01', armedNight: '02', entryDelay: '', exitDelay: '']
|
||||
if (mode[armMode] != '')
|
||||
{
|
||||
return ["raw 0x501 {09 01 04 ${mode[armMode]}00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 100']
|
||||
}
|
||||
}
|
||||
|
||||
def acknowledgeArmRequest(armMode){
|
||||
List cmds = [
|
||||
"raw 0x501 {09 01 00 0${armMode}}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 100"
|
||||
]
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
log.trace "Method: acknowledgeArmRequest(armMode): "+results
|
||||
return results
|
||||
}
|
||||
|
||||
def sendInvalidKeycodeResponse(){
|
||||
List cmds = [
|
||||
"raw 0x501 {09 01 00 04}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 100"
|
||||
]
|
||||
|
||||
log.trace 'Method: sendInvalidKeycodeResponse(): '+cmds
|
||||
return (cmds?.collect { new physicalgraph.device.HubAction(it) }) + sendStatusToDevice()
|
||||
}
|
||||
|
||||
def beep(def beepLength = settings.beepLength) {
|
||||
if ( beepLength == null )
|
||||
{
|
||||
beepLength = 0
|
||||
}
|
||||
def len = zigbee.convertToHexString(beepLength, 2)
|
||||
List cmds = ["raw 0x501 {09 01 04 05${len}}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
|
||||
//------Utility methods------//
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
//------------------------//
|
||||
|
||||
private testCmd(){
|
||||
//log.trace zigbee.parse('catchall: 0104 0501 01 01 0140 00 4F2D 01 00 0000 07 00 ')
|
||||
//beep(10)
|
||||
//test exit delay
|
||||
//log.debug device.zigbeeId
|
||||
//testingTesting()
|
||||
//discoverCmds()
|
||||
//zigbee.configureReporting(1,0x20,0x20,3600,43200,0x01) //battery reporting
|
||||
//["raw 0x0001 {00 00 06 00 2000 20 100E FEFF 01}",
|
||||
//"send 0x${device.deviceNetworkId} 1 1"]
|
||||
//zigbee.command(0x0003, 0x00, "0500") //Identify: blinks connection light
|
||||
|
||||
//log.debug //temperature reporting
|
||||
|
||||
return zigbee.readAttribute(0x0020,0x01) +
|
||||
zigbee.readAttribute(0x0020,0x02) +
|
||||
zigbee.readAttribute(0x0020,0x03)
|
||||
}
|
||||
|
||||
private discoverCmds(){
|
||||
List cmds = ["raw 0x0501 {08 01 11 0011}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
|
||||
private testingTesting() {
|
||||
log.debug "Delay: "+device.currentState("armMode").toString()
|
||||
List cmds = ["raw 0x501 {09 01 04 050A}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
@@ -103,7 +103,7 @@ metadata {
|
||||
}
|
||||
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "illuminance", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
|
||||
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
|
||||
@@ -410,4 +410,4 @@ private command(physicalgraph.zwave.Command cmd) {
|
||||
private commands(commands, delay=200) {
|
||||
log.info "sending commands: ${commands}"
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ metadata {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "luminosity", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
@@ -282,5 +282,4 @@ private secure(physicalgraph.zwave.Command cmd) {
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,7 +79,7 @@ metadata {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "luminosity", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
@@ -193,4 +193,4 @@ def configure() {
|
||||
// set data reporting period to 5 minutes
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 300).format()
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,8 @@
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
@@ -86,7 +86,7 @@
|
||||
}
|
||||
standardTile("tamper", "device.alarm") {
|
||||
state("secure", label:'secure', icon:"st.locks.lock.locked", backgroundColor:"#ffffff")
|
||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0")
|
||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#00a0dc")
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
|
||||
@@ -62,8 +62,8 @@ metadata {
|
||||
state "off", label: '${name}', action: "on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
}
|
||||
standardTile("contact", "device.contact", inactiveLabel: false) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
|
||||
@@ -4,7 +4,7 @@ Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/works-with-smartthings/other/fortrezz-water-valve)
|
||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/products/fortrezz-siren-strobe-alarm)
|
||||
|
||||
## Table of contents
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ metadata {
|
||||
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 "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", 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 "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -56,8 +56,8 @@ metadata {
|
||||
state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#00A0DC")
|
||||
}
|
||||
standardTile("contact", "device.contact") {
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", decoration: "flat") {
|
||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#00A0DC")
|
||||
|
||||
@@ -22,16 +22,16 @@ metadata {
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821", action: "open")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e", action: "close")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC", action: "open")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13", action: "close")
|
||||
}
|
||||
standardTile("freezerDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
}
|
||||
standardTile("mainDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
}
|
||||
standardTile("control", "device.contact", width: 1, height: 1, decoration: "flat") {
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", action: "open")
|
||||
|
||||
@@ -25,8 +25,8 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
standardTile("contact", "device.contact", width: 4, height: 4) {
|
||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#e86d13")
|
||||
}
|
||||
childDeviceTile("freezerDoor", "freezerDoor", height: 2, width: 2, childTileName: "freezerDoor")
|
||||
childDeviceTile("mainDoor", "mainDoor", height: 2, width: 2, childTileName: "mainDoor")
|
||||
|
||||
@@ -27,7 +27,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", 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 "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||
@@ -59,7 +59,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
||||
|
||||
@@ -40,11 +40,11 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#cccccc"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
@@ -50,11 +50,11 @@
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
|
||||
@@ -25,6 +25,7 @@ metadata {
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM 10 Year", deviceJoinName: "SYLVANIA Smart 10-Year A19"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||
|
||||
@@ -46,9 +46,9 @@ metadata {
|
||||
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.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -41,9 +41,9 @@ metadata {
|
||||
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.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ metadata {
|
||||
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.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -49,9 +49,9 @@ metadata {
|
||||
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.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
2
devicetypes/smartthings/zwave-water-valve.src/.st-ignore
Normal file
2
devicetypes/smartthings/zwave-water-valve.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
38
devicetypes/smartthings/zwave-water-valve.src/README.md
Normal file
38
devicetypes/smartthings/zwave-water-valve.src/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Z-Wave Water Valve
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Leak Intelligence Leak Gopher Water Shutoff Valve](https://www.smartthings.com/works-with-smartthings/other/leak-intelligence-leak-gopher-water-shutoff-valve)
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#Troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
* **Valve** - allows for the control of a valve device
|
||||
* **Polling** - represents that poll() can be implemented for the device
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Sensor** - detects sensor events
|
||||
|
||||
## Device Health
|
||||
|
||||
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [Leak Intelligence Leak Gopher Water Shutoff Valve Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/209631423-Leak-Gopher-Z-Wave-Valve-Control)
|
||||
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
metadata {
|
||||
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Health Check"
|
||||
capability "Valve"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint deviceId: "0x1006", inClusters: "0x25"
|
||||
fingerprint mfr:"0173", prod:"0003", model:"0002", deviceJoinName: "Leak Intelligence Leak Gopher Water Shutoff Valve"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -53,7 +55,14 @@ metadata {
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
response(refresh())
|
||||
}
|
||||
|
||||
@@ -114,6 +123,13 @@ def poll() {
|
||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() is called"
|
||||
def commands = [zwave.switchBinaryV1.switchBinaryGet().format()]
|
||||
|
||||
1545
smartapps/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
1545
smartapps/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
File diff suppressed because it is too large
Load Diff
@@ -765,7 +765,6 @@ def turnOffSwitch() {
|
||||
} else {
|
||||
|
||||
device.off();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
@@ -789,6 +788,7 @@ def getTempSensorsStatus(id) {
|
||||
return []
|
||||
} else {
|
||||
def bat = getBatteryStatus(device.id)
|
||||
return [temperature: device.currentValue('temperature')] + bat
|
||||
def scale = [Scale: location.temperatureScale]
|
||||
return [temperature: device.currentValue('temperature')] + bat + scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,66 +15,66 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Light switches to turn off") {
|
||||
input "switches", "capability.switch", title: "Choose light switches", multiple: true
|
||||
}
|
||||
section("Turn off when there is no motion and presence") {
|
||||
input "motionSensor", "capability.motionSensor", title: "Choose motion sensor"
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Choose presence sensors", multiple: true
|
||||
}
|
||||
section("Delay before turning off") {
|
||||
input "delayMins", "number", title: "Minutes of inactivity?"
|
||||
}
|
||||
section("Light switches to turn off") {
|
||||
input "switches", "capability.switch", title: "Choose light switches", multiple: true
|
||||
}
|
||||
section("Turn off when there is no motion and presence") {
|
||||
input "motionSensor", "capability.motionSensor", title: "Choose motion sensor"
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Choose presence sensors", multiple: true
|
||||
}
|
||||
section("Delay before turning off") {
|
||||
input "delayMins", "number", title: "Minutes of inactivity?"
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
unsubscribe()
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
}
|
||||
|
||||
def motionHandler(evt) {
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "inactive") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "inactive") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||
}
|
||||
}
|
||||
|
||||
def presenceHandler(evt) {
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||
}
|
||||
}
|
||||
|
||||
def isActivePresence() {
|
||||
// check all the presence sensors, make sure none are present
|
||||
def noPresence = presenceSensors.find{it.currentPresence == "present"} == null
|
||||
!noPresence
|
||||
// check all the presence sensors, make sure none are present
|
||||
def noPresence = presenceSensors.find{it.currentPresence == "present"} == null
|
||||
!noPresence
|
||||
}
|
||||
|
||||
def scheduleCheck() {
|
||||
log.debug "scheduled check"
|
||||
def motionState = motionSensor.currentState("motion")
|
||||
log.debug "scheduled check"
|
||||
def motionState = motionSensor.currentState("motion")
|
||||
if (motionState.value == "inactive") {
|
||||
def elapsed = now() - motionState.rawDateCreated.time
|
||||
def threshold = 1000 * 60 * delayMins - 1000
|
||||
if (elapsed >= threshold) {
|
||||
if (!isActivePresence()) {
|
||||
log.debug "Motion has stayed inactive since last check ($elapsed ms) and no presence: turning lights off"
|
||||
switches.off()
|
||||
} else {
|
||||
log.debug "Presence is active: do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms): do nothing"
|
||||
def elapsed = now() - motionState.rawDateCreated.time
|
||||
def threshold = 1000 * 60 * delayMins - 1000
|
||||
if (elapsed >= threshold) {
|
||||
if (!isActivePresence()) {
|
||||
log.debug "Motion has stayed inactive since last check ($elapsed ms) and no presence: turning lights off"
|
||||
switches.off()
|
||||
} else {
|
||||
log.debug "Presence is active: do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms): do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion is active: do nothing"
|
||||
log.debug "Motion is active: do nothing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,17 @@ def registerAllDeviceSubscriptions() {
|
||||
registerChangeHandler(inputs)
|
||||
}
|
||||
|
||||
//Subscribe to events from a list of devices
|
||||
def registerChangeHandler(myList) {
|
||||
myList.each { myDevice ->
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
theAtts.each { att ->
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to events from a specific device
|
||||
def registerDeviceChange() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
|
||||
@@ -27,84 +27,82 @@ definition(
|
||||
|
||||
preferences {
|
||||
|
||||
section("Monitor this door or window") {
|
||||
input "contact", "capability.contactSensor"
|
||||
}
|
||||
section("And notify me if it's open for more than this many minutes (default 10)") {
|
||||
input "openThreshold", "number", description: "Number of minutes", required: false
|
||||
}
|
||||
section("Delay between notifications (default 10 minutes") {
|
||||
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
||||
section("Monitor this door or window") {
|
||||
input "contact", "capability.contactSensor"
|
||||
}
|
||||
|
||||
section("And notify me if it's open for more than this many minutes (default 10)") {
|
||||
input "openThreshold", "number", description: "Number of minutes", required: false
|
||||
}
|
||||
|
||||
section("Delay between notifications (default 10 minutes") {
|
||||
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
||||
}
|
||||
|
||||
section("Via text message at this number (or via push notification if not specified") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone", "phone", title: "Phone number (optional)", required: false
|
||||
}
|
||||
section("Via text message at this number (or via push notification if not specified") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone", "phone", title: "Phone number (optional)", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.trace "installed()"
|
||||
subscribe()
|
||||
log.trace "installed()"
|
||||
subscribe()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.trace "updated()"
|
||||
unsubscribe()
|
||||
subscribe()
|
||||
log.trace "updated()"
|
||||
unsubscribe()
|
||||
subscribe()
|
||||
}
|
||||
|
||||
def subscribe() {
|
||||
subscribe(contact, "contact.open", doorOpen)
|
||||
subscribe(contact, "contact.closed", doorClosed)
|
||||
subscribe(contact, "contact.open", doorOpen)
|
||||
subscribe(contact, "contact.closed", doorClosed)
|
||||
}
|
||||
|
||||
def doorOpen(evt)
|
||||
{
|
||||
log.trace "doorOpen($evt.name: $evt.value)"
|
||||
def t0 = now()
|
||||
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||
runIn(delay, doorOpenTooLong, [overwrite: false])
|
||||
log.debug "scheduled doorOpenTooLong in ${now() - t0} msec"
|
||||
def doorOpen(evt) {
|
||||
log.trace "doorOpen($evt.name: $evt.value)"
|
||||
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||
runIn(delay, doorOpenTooLong, [overwrite: true])
|
||||
}
|
||||
|
||||
def doorClosed(evt)
|
||||
{
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
def doorClosed(evt) {
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
unschedule(doorOpenTooLong)
|
||||
}
|
||||
|
||||
def doorOpenTooLong() {
|
||||
def contactState = contact.currentState("contact")
|
||||
def freq = (frequency != null && frequency != "") ? frequency * 60 : 600
|
||||
def contactState = contact.currentState("contact")
|
||||
def freq = (frequency != null && frequency != "") ? frequency * 60 : 600
|
||||
|
||||
if (contactState.value == "open") {
|
||||
def elapsed = now() - contactState.rawDateCreated.time
|
||||
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
|
||||
if (elapsed >= threshold) {
|
||||
log.debug "Contact has stayed open long enough since last check ($elapsed ms): calling sendMessage()"
|
||||
sendMessage()
|
||||
runIn(freq, doorOpenTooLong, [overwrite: false])
|
||||
} else {
|
||||
log.debug "Contact has not stayed open long enough since last check ($elapsed ms): doing nothing"
|
||||
}
|
||||
} else {
|
||||
log.warn "doorOpenTooLong() called but contact is closed: doing nothing"
|
||||
}
|
||||
if (contactState.value == "open") {
|
||||
def elapsed = now() - contactState.rawDateCreated.time
|
||||
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
|
||||
if (elapsed >= threshold) {
|
||||
log.debug "Contact has stayed open long enough since last check ($elapsed ms): calling sendMessage()"
|
||||
sendMessage()
|
||||
runIn(freq, doorOpenTooLong, [overwrite: false])
|
||||
} else {
|
||||
log.debug "Contact has not stayed open long enough since last check ($elapsed ms): doing nothing"
|
||||
}
|
||||
} else {
|
||||
log.warn "doorOpenTooLong() called but contact is closed: doing nothing"
|
||||
}
|
||||
}
|
||||
|
||||
void sendMessage()
|
||||
{
|
||||
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
||||
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
||||
log.info msg
|
||||
if (location.contactBookEnabled) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
}
|
||||
else {
|
||||
if (phone) {
|
||||
sendSms phone, msg
|
||||
} else {
|
||||
sendPush msg
|
||||
}
|
||||
void sendMessage() {
|
||||
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
||||
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
||||
log.info msg
|
||||
if (location.contactBookEnabled) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
} else {
|
||||
if (phone) {
|
||||
sendSms phone, msg
|
||||
} else {
|
||||
sendPush msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,10 @@ def pageHistory() {
|
||||
if (history.size() == 0) {
|
||||
paragraph "No history available."
|
||||
} else {
|
||||
paragraph "Not implemented"
|
||||
history.each() {
|
||||
def text = "" + new Date(it.time + location.timeZone.rawOffset ).format("yyyy-MM-dd HH:mm") + ": " + it.event + " - " + it.description
|
||||
paragraph text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +408,7 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (contact)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"exterior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:true
|
||||
input "chime_${devId}", "bool", title:"Chime on open", defaultValue:true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,6 +420,7 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (motion)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"interior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on motion", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,6 +432,7 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (movement)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"interior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on movement", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,6 +444,7 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (smoke)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"alert"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on smoke", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,6 +456,7 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (moisture)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"alert"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on water", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,6 +479,14 @@ def pageArmingOptions() {
|
||||
"is armed without setting off an alarm. You can optionally disable " +
|
||||
"entry and exit delay when the alarm is armed in Stay mode."
|
||||
|
||||
def inputKeypads = [
|
||||
name: "keypads",
|
||||
type: "capability.lockCodes",
|
||||
title: "Keypads for Exit / Entry delay",
|
||||
multiple: true,
|
||||
required: false
|
||||
]
|
||||
|
||||
def inputAwayModes = [
|
||||
name: "awayModes",
|
||||
type: "mode",
|
||||
@@ -507,10 +523,19 @@ def pageArmingOptions() {
|
||||
def inputDelayStay = [
|
||||
name: "stayDelayOff",
|
||||
type: "bool",
|
||||
title: "Disable delays in Stay mode",
|
||||
title: "Disable alarm (entry) delay in Stay mode",
|
||||
defaultValue: false,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputExitDelayStay = [
|
||||
name: "stayExitDelayOff",
|
||||
type: "bool",
|
||||
title: "Disable arming (exit) delay in Stay mode",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
|
||||
def pageProperties = [
|
||||
name: "pageArmingOptions",
|
||||
@@ -524,16 +549,21 @@ def pageArmingOptions() {
|
||||
paragraph helpArming
|
||||
}
|
||||
|
||||
section("Keypads") {
|
||||
input inputKeypads
|
||||
}
|
||||
|
||||
section("Modes") {
|
||||
input inputAwayModes
|
||||
input inputStayModes
|
||||
input inputDisarmModes
|
||||
}
|
||||
|
||||
|
||||
section("Exit and Entry Delay") {
|
||||
paragraph helpDelay
|
||||
input inputDelay
|
||||
input inputDelayStay
|
||||
input inputExitDelayStay
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,6 +593,14 @@ def pageAlarmOptions() {
|
||||
defaultValue: "Both"
|
||||
]
|
||||
|
||||
def inputSirenEntryStrobe = [
|
||||
name: "sirenEntryStrobe",
|
||||
type: "bool",
|
||||
title: "Strobe siren during entry delay",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSwitches = [
|
||||
name: "switches",
|
||||
type: "capability.switch",
|
||||
@@ -602,6 +640,7 @@ def pageAlarmOptions() {
|
||||
section("Sirens") {
|
||||
input inputAlarms
|
||||
input inputSirenMode
|
||||
input inputSirenEntryStrobe
|
||||
}
|
||||
section("Switches") {
|
||||
input inputSwitches
|
||||
@@ -624,7 +663,49 @@ def pageNotifications() {
|
||||
"disarmed or when an alarm is set off. Notifications can be send " +
|
||||
"using either Push messages, SMS (text) messages and Pushbullet " +
|
||||
"messaging service. Smart Alarm can also notify you with sounds or " +
|
||||
"voice alerts using compatible audio devices, such as Sonos."
|
||||
"voice alerts using compatible audio devices, such as Sonos." +
|
||||
"Or using a SmartAlarm dashboard virtual device."
|
||||
|
||||
def inputNotificationDevice = [
|
||||
name: "notificationDevice",
|
||||
type: "capability.notification",
|
||||
title: "Which smart alarm notification device?",
|
||||
multiple: false,
|
||||
required: false
|
||||
]
|
||||
|
||||
def inputChimeDevices = [
|
||||
name: "chimeDevices",
|
||||
type: "capability.tone",
|
||||
title: "Which Chime Devices?",
|
||||
multiple: true,
|
||||
required: false
|
||||
]
|
||||
|
||||
|
||||
def inputSirenOnWaterAlert = [
|
||||
name: "sirenOnWaterAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Water Leak?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSirenOnSmokeAlert = [
|
||||
name: "sirenOnSmokeAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Smoke Alert?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSirenOnIntrusionAlert = [
|
||||
name: "sirenOnIntrusionAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Intrusion Alert?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputPushAlarm = [
|
||||
name: "pushMessage",
|
||||
@@ -807,6 +888,19 @@ def pageNotifications() {
|
||||
section("Notification Options") {
|
||||
paragraph helpAbout
|
||||
}
|
||||
section("Notification Device")
|
||||
{
|
||||
input inputNotificationDevice
|
||||
}
|
||||
section("Chime Devices") {
|
||||
input inputChimeDevices
|
||||
}
|
||||
section("Siren Notifcations")
|
||||
{
|
||||
input inputSirenOnWaterAlert
|
||||
input inputSirenOnSmokeAlert
|
||||
input inputSirenOnIntrusionAlert
|
||||
}
|
||||
section("Push Notifications") {
|
||||
input inputPushAlarm
|
||||
input inputPushStatus
|
||||
@@ -1047,6 +1141,7 @@ private def setupInit() {
|
||||
state.zones = []
|
||||
state.alarms = []
|
||||
state.history = []
|
||||
state.alertType = "None"
|
||||
} else {
|
||||
def version = state.version as String
|
||||
if (version == null || version.startsWith('1')) {
|
||||
@@ -1065,7 +1160,7 @@ private def initialize() {
|
||||
clearAlarm()
|
||||
state.delay = settings.delay?.toInteger() ?: 30
|
||||
state.offSwitches = []
|
||||
state.history = []
|
||||
//state.history = []
|
||||
|
||||
if (settings.awayModes?.contains(location.mode)) {
|
||||
state.armed = true
|
||||
@@ -1082,8 +1177,20 @@ private def initialize() {
|
||||
initButtons()
|
||||
initRestApi()
|
||||
subscribe(location, onLocation)
|
||||
|
||||
if (settings.notificationDevice)
|
||||
{
|
||||
subscribe(settings.notificationDevice, "switch.off", gotDismissMessage)
|
||||
}
|
||||
|
||||
STATE()
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def gotDismissMessage(evt)
|
||||
{
|
||||
log.debug "Got the dismiss message from the notification device.. clearing alarm!"
|
||||
clearAlarm()
|
||||
}
|
||||
|
||||
private def clearAlarm() {
|
||||
@@ -1103,6 +1210,7 @@ private def clearAlarm() {
|
||||
}
|
||||
state.offSwitches = []
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
private def initZones() {
|
||||
@@ -1123,7 +1231,8 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "contact",
|
||||
zoneType: settings["type_${it.id}"] ?: "exterior",
|
||||
delay: settings["delay_${it.id}"]
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_contact, "contact.open", onContact)
|
||||
@@ -1135,7 +1244,8 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "motion",
|
||||
zoneType: settings["type_${it.id}"] ?: "interior",
|
||||
delay: settings["delay_${it.id}"]
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_motion, "motion.active", onMotion)
|
||||
@@ -1147,7 +1257,8 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "acceleration",
|
||||
zoneType: settings["type_${it.id}"] ?: "interior",
|
||||
delay: settings["delay_${it.id}"]
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_movement, "acceleration.active", onMovement)
|
||||
@@ -1159,7 +1270,8 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "smoke",
|
||||
zoneType: settings["type_${it.id}"] ?: "alert",
|
||||
delay: settings["delay_${it.id}"]
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_smoke, "smoke.detected", onSmoke)
|
||||
@@ -1174,7 +1286,8 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "water",
|
||||
zoneType: settings["type_${it.id}"] ?: "alert",
|
||||
delay: settings["delay_${it.id}"]
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_water, "water.wet", onWater)
|
||||
@@ -1257,20 +1370,37 @@ def onWater(evt) { onZoneEvent(evt, "water") }
|
||||
private def onZoneEvent(evt, sensorType) {
|
||||
LOG("onZoneEvent(${evt.displayName}, ${sensorType})")
|
||||
|
||||
state.alertType = sensorType
|
||||
def zone = getZoneForDevice(evt.deviceId, sensorType)
|
||||
if (!zone) {
|
||||
log.warn "Cannot find zone for device ${evt.deviceId}"
|
||||
state.alertType = "None"
|
||||
return
|
||||
}
|
||||
|
||||
if (zone.armed) {
|
||||
state.alarms << evt.displayName
|
||||
if (zone.zoneType == "alert" || !zone.delay || (state.stay && settings.stayDelayOff)) {
|
||||
history("Alarm", "Alarm triggered by ${sensorType} sensor ${evt.displayName}")
|
||||
activateAlarm()
|
||||
} else {
|
||||
history("Entry Delay", "Entry delay triggered by ${sensorType} sensor ${evt.displayName}")
|
||||
if(settings.sirenEntryStrobe)
|
||||
{
|
||||
settings.alarms*.strobe()
|
||||
}
|
||||
keypads?.each() { it.setEntryDelay(state.delay) }
|
||||
myRunIn(state.delay, activateAlarm)
|
||||
}
|
||||
}
|
||||
else if (zone.chime)
|
||||
{
|
||||
chimeDevices?.each() {
|
||||
it.beep()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
def onLocation(evt) {
|
||||
@@ -1311,23 +1441,27 @@ def onButtonEvent(evt) {
|
||||
|
||||
def armAway() {
|
||||
LOG("armAway()")
|
||||
history("Armed Away", "Alarm armed away")
|
||||
|
||||
if (!atomicState.armed || atomicState.stay) {
|
||||
armPanel(false)
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def armStay() {
|
||||
LOG("armStay()")
|
||||
history("Armed Stay", "Alarm armed stay")
|
||||
|
||||
if (!atomicState.armed || !atomicState.stay) {
|
||||
armPanel(true)
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def disarm() {
|
||||
LOG("disarm()")
|
||||
|
||||
history("Disarmed", "Alarm disarmed")
|
||||
if (atomicState.armed) {
|
||||
state.armed = false
|
||||
state.zones.each() {
|
||||
@@ -1335,9 +1469,12 @@ def disarm() {
|
||||
it.armed = false
|
||||
}
|
||||
}
|
||||
|
||||
keypads?.each() { it.setDisarmed() }
|
||||
|
||||
reset()
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def panic() {
|
||||
@@ -1364,6 +1501,7 @@ def reset() {
|
||||
|
||||
notify(msg)
|
||||
notifyVoice()
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def exitDelayExpired() {
|
||||
@@ -1383,6 +1521,16 @@ def exitDelayExpired() {
|
||||
it.armed = true
|
||||
}
|
||||
}
|
||||
|
||||
if(stay)
|
||||
{
|
||||
keypads?.each() { it.setArmedStay() }
|
||||
}
|
||||
else
|
||||
{
|
||||
keypads?.each() { it.setArmedAway() }
|
||||
}
|
||||
|
||||
|
||||
def msg = "${location.name}: all "
|
||||
if (stay) {
|
||||
@@ -1406,7 +1554,7 @@ private def armPanel(stay) {
|
||||
state.zones.each() {
|
||||
def zoneType = it.zoneType
|
||||
if (zoneType == "exterior") {
|
||||
if (it.delay) {
|
||||
if (it.delay && !(stay && settings.stayExitDelayOff)) {
|
||||
it.armed = false
|
||||
armDelay = true
|
||||
} else {
|
||||
@@ -1424,10 +1572,22 @@ private def armPanel(stay) {
|
||||
}
|
||||
}
|
||||
|
||||
def delay = armDelay && !(stay && settings.stayDelayOff) ? atomicState.delay : 0
|
||||
def delay = armDelay && !(stay && settings.stayExitDelayOff) ? atomicState.delay : 0
|
||||
if (delay) {
|
||||
keypads?.each() { it.setExitDelay(delay) }
|
||||
myRunIn(delay, exitDelayExpired)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(stay)
|
||||
{
|
||||
keypads?.each() { it.setArmedStay() }
|
||||
}
|
||||
else
|
||||
{
|
||||
keypads?.each() { it.setArmedAway() }
|
||||
}
|
||||
}
|
||||
|
||||
def mode = stay ? "STAY" : "AWAY"
|
||||
def msg = "${location.name} "
|
||||
@@ -1532,21 +1692,50 @@ def activateAlarm() {
|
||||
log.warn "activateAlarm: false alarm"
|
||||
return
|
||||
}
|
||||
history("Alarm", "Alarm Triggered")
|
||||
|
||||
switch (settings.sirenMode) {
|
||||
case "Siren":
|
||||
settings.alarms*.siren()
|
||||
break
|
||||
|
||||
case "Strobe":
|
||||
settings.alarms*.strobe()
|
||||
break
|
||||
|
||||
case "Both":
|
||||
settings.alarms*.both()
|
||||
break
|
||||
if(settings.sirenEntryStrobe)
|
||||
{
|
||||
settings.alarms*.off()
|
||||
}
|
||||
|
||||
def atype = state.alertType
|
||||
|
||||
if ((atype == "Water" && settings.sirenOnWaterAlert) ||
|
||||
(atype == "Smoke" && settings.sirenOnSmokeAlert) ||
|
||||
((atype == "contact" || atype == "acceleration" || atype == "motion") && settings.sirenOnIntrusionAlert))
|
||||
{
|
||||
switch (settings.sirenMode) {
|
||||
case "Siren":
|
||||
settings.alarms*.siren()
|
||||
break
|
||||
|
||||
case "Strobe":
|
||||
settings.alarms*.strobe()
|
||||
break
|
||||
|
||||
case "Both":
|
||||
settings.alarms*.both()
|
||||
break
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "No siren for $atype Alert"
|
||||
}
|
||||
}
|
||||
|
||||
def activateAlarmPostDelay(String lastAlertType)
|
||||
{
|
||||
// no alarm check here as if door opens only for second with delay and system is not disarmed
|
||||
// we still want alarm even if door is closed after delay.. Basically like real alarm the delay is only
|
||||
// to disarm the system. Otherwise someone can open door come it, quickly close and there is not alarm LGK.
|
||||
|
||||
// issue here is that after delay we could have lost the alert type so pass it in
|
||||
log.debug "activate alarm post delay check - alert type = $lastAlertType"
|
||||
|
||||
activateSirenAfterCheck(lastAlertType)
|
||||
|
||||
// Only turn on those switches that are currently off
|
||||
def switchesOn = settings.switches?.findAll { it?.currentSwitch == "off" }
|
||||
LOG("switchesOn: ${switchesOn}")
|
||||
@@ -1570,6 +1759,8 @@ def activateAlarm() {
|
||||
notify(msg)
|
||||
notifyVoice()
|
||||
|
||||
reportStatus()
|
||||
|
||||
myRunIn(180, reset)
|
||||
}
|
||||
|
||||
@@ -1668,12 +1859,61 @@ private def notifyVoice() {
|
||||
}
|
||||
}
|
||||
|
||||
def reportStatus()
|
||||
{
|
||||
log.debug "in report status"
|
||||
log.debug "notification device = ${settings.notificationDevice}"
|
||||
|
||||
if (settings.notificationDevice)
|
||||
{
|
||||
def phrase = ""
|
||||
if (state.alarms.size())
|
||||
{
|
||||
phrase = "Alert: Alarm at ${location.name}!"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
log.debug "sending notification alert: = $phrase"
|
||||
def zones = "Zones: "
|
||||
state.alarms.each()
|
||||
{
|
||||
//log.debug "in loop it"
|
||||
//log.debug "it = $it"
|
||||
zones = "Zones: "
|
||||
zones += " $it" +"\n"
|
||||
}
|
||||
notificationDevice.deviceNotification(zones)
|
||||
log.debug "sending nofication zones = $zones"
|
||||
|
||||
// send zone type
|
||||
phrase = "AlertType: "
|
||||
def atype = state.alertType
|
||||
if (atype == null)
|
||||
atype = "None"
|
||||
phrase += " $atype"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
log.debug "sending nofication alert type = $phrase"
|
||||
}
|
||||
else
|
||||
{
|
||||
phrase = "Status: "
|
||||
if (state.armed)
|
||||
{
|
||||
def mode = state.stay ? "Armed - Stay" : "Armed - Away"
|
||||
phrase += "${mode}"
|
||||
} else {
|
||||
phrase += "Disarmed"
|
||||
}
|
||||
log.debug "sending notification status = $phrase"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def history(String event, String description = "") {
|
||||
LOG("history(${event}, ${description})")
|
||||
|
||||
def history = atomicState.history
|
||||
history << [time: now(), event: event, description: description]
|
||||
if (history.size() > 10) {
|
||||
if (history.size() > 20) {
|
||||
history = history.sort{it.time}
|
||||
history = history[1..-1]
|
||||
}
|
||||
@@ -1850,3 +2090,27 @@ private def LOG(message) {
|
||||
private def STATE() {
|
||||
//log.trace "state: ${state}"
|
||||
}
|
||||
|
||||
def onAlarmSystemStatus(evt) {
|
||||
LOG("Alarm System Status has been changed to '${evt.value}'")
|
||||
String mode = evt.value.toLowerCase()
|
||||
if (mode == "away") {
|
||||
armAway()
|
||||
} else if (mode == "stay") {
|
||||
armStay()
|
||||
} else if (mode == "off") {
|
||||
disarm()
|
||||
}
|
||||
}
|
||||
|
||||
def setAlarmMode(name) {
|
||||
LOG("Alarm System Status will be set to '${name}'")
|
||||
def event = [
|
||||
name: "alarmSystemStatus",
|
||||
value: name,
|
||||
isStateChange: true,
|
||||
displayed: true,
|
||||
description: "alarm system status is ${name}",
|
||||
]
|
||||
sendLocationEvent(event)
|
||||
}
|
||||
Reference in New Issue
Block a user