Compare commits

...

25 Commits

Author SHA1 Message Date
Raffaele Giannattasio
8583c42282 MSA-1993: keypad 2017-05-20 12:36:16 -07:00
Jack Chi
01c2968f91 Merge pull request #1834 from parijatdas/zwave_water_valve
[CHF-569] Health Check zwave-water-valve
2017-05-17 17:05:10 -07:00
Vinay Rao
40b75ce665 Merge pull request #2006 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-16 15:37:10 -07:00
Vinay Rao
9b01a7d8be Merge pull request #2004 from SmartThingsCommunity/production
Rolling down production to staging
2017-05-16 14:37:15 -07:00
Parijat Das
12bb6c0492 Added health-check for Z-wave Water Valve 2017-05-16 12:26:48 +05:30
Vinay Rao
7cf8bb1917 Merge pull request #1998 from larsfinander/DVCSMP-2656_OpenT2T_Update_to_5_5_submission_staging
DVCSMP-2656 OpenT2T: Update to 5/15 submission
2017-05-15 12:31:34 -07:00
Lars Finander
d4fd778a64 DVCSMP-2656 OpenT2T: Update to 5/15 submission 2017-05-15 13:25:44 -06:00
Jack Chi
eb870e5f31 Merge pull request #1943 from parijatdas/smartalert_siren
[CHF-590] Fixed typo error
2017-05-15 10:47:25 -07:00
Vinay Rao
d60657e466 Merge pull request #1992 from dkirker/production
PROB-1615: Add SYLVANIA Smart 10-Year A19 bulb fingerprint
2017-05-11 15:08:13 -07:00
Donald Kirker
d8dc70ae9e PROB-1615 Add SYLVANIA Smart 10-Year A19 bulb fingerprint 2017-05-11 14:03:24 -07:00
Vinay Rao
a99e050c6b Merge pull request #1975 from jackchi/device-ux-remaining
[DVCSMP-2607] Standardize remaining SmartThingsPublic DTH colors
2017-05-11 13:35:42 -07:00
jackchi
5bf7caca0d [DVCSMP-2607] Standardize remaining SmartThingsPublic ON/CLOSED/TAMPERED & OPEN state colors 2017-05-11 12:23:38 -07:00
Vinay Rao
3457bbad42 Merge pull request #1991 from tslagle13/gideon-change
[MSA-1968] - Publish gideon smarthome changes
2017-05-11 11:12:30 -07:00
tslagle13
c6c4b09fbb [MSA-1968] - Publish gideon smarthome changes
small change to sensor readings
2017-05-11 11:06:44 -07:00
Aaron Miller
3eddd68532 Merge pull request #1978 from aaron-miller/DVCSMP-2612
[DVCSMP-2612] Unschedule execution for Left It Open when door is closed
2017-05-11 08:51:36 -05:00
Aaron Miller
8d79379bba Merge pull request #1927 from aaron-miller/DVCSMP-2595
[DVCSMP-2595] Set scheduled executions for Lights off with no motion SA to overwrite
2017-05-10 13:29:03 -05:00
Vinay Rao
7e25d32c70 Merge pull request #1990 from SmartThingsCommunity/master
Rolling up master to staging
2017-05-09 16:04:19 -05:00
Vinay Rao
dd4da29bcd Merge pull request #1989 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-09 16:03:53 -05:00
Aaron Miller
b6136bf1d5 [DVCSMP-2612] Unschedule execution for Left It Open when door is closed 2017-05-05 13:22:46 -05:00
Vinay Rao
68f5cda945 Merge pull request #1977 from ccurtiST/PROB-1347
PROB-1347 Lux values showing as ${unit} in recently tab of Aeon Multipurpose
2017-05-05 11:06:46 -07:00
Christopher Curti
da42ee63fb Replaced ${unit}', unit:"lux" with lux', unit: "" to fix issue with seeing ${unit} in the recently tab of Aeon multisensor illuminance values. Tested all three DTHs and lux was displayed rather then ${unit} 2017-05-05 09:31:44 -07:00
Vinay Rao
c58132a69e Merge pull request #1974 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-03 11:54:21 -07:00
Vinay Rao
f069ea3087 Merge pull request #1965 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-02 16:14:39 -07:00
Parijat Das
1d629cfc9a Fixed typo in README url 2017-04-25 11:50:10 +05:30
Aaron Miller
5b7a7097b8 [DVCSMP-2595] Set scheduled executions for Lights off with no motion SA to overwrite 2017-04-20 13:20:55 -05:00
33 changed files with 4406 additions and 198 deletions

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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") {

View File

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

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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()
])
}
}

View File

@@ -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:""

View File

@@ -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"

View File

@@ -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

View File

@@ -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") {

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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") {

View File

@@ -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: ''

View File

@@ -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") {

View File

@@ -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"

View File

@@ -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") {

View File

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

View File

@@ -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") {

View File

@@ -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") {

View File

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

View 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)

View File

@@ -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()]

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}
}

View File

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

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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)
}