Compare commits

..

1 Commits

Author SHA1 Message Date
이상규
d4f4c8711f MSA-931: This is test 2016-03-08 03:22:47 -06:00
3 changed files with 729 additions and 410 deletions

View File

@@ -1,93 +0,0 @@
/**
* TEST
*
* Copyright 2016 박춘영
*
* 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: "TEST", namespace: "스마트보안", author: "박춘영") {
capability "Button"
capability "Samsung TV"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
// TODO: define your main and details tiles here
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
// TODO: handle 'button' attribute
// TODO: handle 'volume' attribute
// TODO: handle 'mute' attribute
// TODO: handle 'pictureMode' attribute
// TODO: handle 'soundMode' attribute
// TODO: handle 'switch' attribute
// TODO: handle 'messageButton' attribute
}
// handle commands
def volumeUp() {
log.debug "Executing 'volumeUp'"
// TODO: handle 'volumeUp' command
}
def volumeDown() {
log.debug "Executing 'volumeDown'"
// TODO: handle 'volumeDown' command
}
def setVolume() {
log.debug "Executing 'setVolume'"
// TODO: handle 'setVolume' command
}
def mute() {
log.debug "Executing 'mute'"
// TODO: handle 'mute' command
}
def unmute() {
log.debug "Executing 'unmute'"
// TODO: handle 'unmute' command
}
def setPictureMode() {
log.debug "Executing 'setPictureMode'"
// TODO: handle 'setPictureMode' command
}
def setSoundMode() {
log.debug "Executing 'setSoundMode'"
// TODO: handle 'setSoundMode' command
}
def on() {
log.debug "Executing 'on'"
// TODO: handle 'on' command
}
def off() {
log.debug "Executing 'off'"
// TODO: handle 'off' command
}
def showMessage() {
log.debug "Executing 'showMessage'"
// TODO: handle 'showMessage' command
}

View File

@@ -0,0 +1,412 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* SmartSense Virtual OpenClosed
*
* Author: SmartThings
* Date: 2013-03-07
*/
metadata {
definition (name: "가상열기닫기센서", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
capability "Contact Sensor"
capability "Acceleration Sensor"
capability "Signal Strength"
capability "Temperature Measurement"
capability "Sensor"
capability "Battery"
}
simulator {
status "open": "zone report :: type: 19 value: 0031"
status "closed": "zone report :: type: 19 value: 0030"
status "acceleration": "acceleration: 1, rssi: 0, lqi: 0"
status "no acceleration": "acceleration: 0, rssi: 0, lqi: 0"
for (int i = 20; i <= 100; i += 10) {
status "${i}F": "contactState: 0, accelerationState: 0, temp: $i F, battery: 100, rssi: 100, lqi: 255"
}
// kinda hacky because it depends on how it is installed
status "x,y,z: 0,0,0": "x: 0, y: 0, z: 0, rssi: 100, lqi: 255"
status "x,y,z: 1000,0,0": "x: 1000, y: 0, z: 0, rssi: 100, lqi: 255"
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0, rssi: 100, lqi: 255"
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000, rssi: 100, lqi: 255"
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
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")
}
standardTile("acceleration", "device.acceleration") {
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
}
valueTile("temperature", "device.temperature") {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[
[value: 5, color: "#BC2323"],
[value: 10, color: "#D04E00"],
[value: 15, color: "#F1D801"],
[value: 16, color: "#FFFFFF"]
]*/
}
/*
valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) {
state "lqi", label:'${currentValue}% signal', unit:""
}
*/
main(["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "3axis", "battery"/*, "lqi"*/])
}
}
def parse(String description) {
def results
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
// Ignore this in favor of orientation-based state
// results = parseSingleMessage(description)
}
else {
results = parseMultiSensorMessage(description)
}
log.debug "Parse returned $results.descriptionText"
return results
}
private List parseMultiSensorMessage(description) {
def results = []
if (isAccelerationMessage(description)) {
results = parseAccelerationMessage(description)
}
else if (isContactMessage(description)) {
results = parseContactMessage(description)
}
else if (isRssiLqiMessage(description)) {
results = parseRssiLqiMessage(description)
}
else if (isOrientationMessage(description)) {
results = parseOrientationMessage(description)
}
results
}
private List parseAccelerationMessage(String description) {
def results = []
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('acceleration:')) {
results << getAccelerationResult(part, description)
}
/*
// TEMPORARILY THROW RSSI & LQI ON THE FLOOR TO SAVE PROCESSING
else if (part.startsWith('rssi:')) {
results << getRssiResult(part, description)
}
else if (part.startsWith('lqi:')) {
results << getLqiResult(part, description)
}
*/
}
results
}
private List parseContactMessage(String description) {
def results = []
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('accelerationState:')) {
results << getAccelerationResult(part, description)
}
else if (part.startsWith('temp:')) {
results << getTempResult(part, description)
}
else if (part.startsWith('battery:')) {
results << getBatteryResult(part, description)
}
/*
// TEMPORARILY THROW RSSI & LQI ON THE FLOOR TO SAVE PROCESSING
else if (part.startsWith('rssi:')) {
results << getRssiResult(part, description)
}
else if (part.startsWith('lqi:')) {
results << getLqiResult(part, description)
}
*/
}
results
}
private List parseOrientationMessage(String description) {
def results = []
def xyzResults = [x: 0, y: 0, z: 0]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('x:')) {
def unsignedX = part.split(":")[1].trim().toInteger()
def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX
xyzResults.x = signedX
}
else if (part.startsWith('y:')) {
def unsignedY = part.split(":")[1].trim().toInteger()
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
xyzResults.y = signedY
}
else if (part.startsWith('z:')) {
def unsignedZ = part.split(":")[1].trim().toInteger()
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
xyzResults.z = signedZ
}
/*
// TEMPORARILY THROW RSSI & LQI ON THE FLOOR TO SAVE PROCESSING
else if (part.startsWith('rssi:')) {
results << getRssiResult(part, description)
}
else if (part.startsWith('lqi:')) {
results << getLqiResult(part, description)
}
*/
}
def xyz = getXyzResult(xyzResults, description)
results << xyz
// Looks for Z-axis orientation as virtual contact state
def a = xyz.value.split(',').collect{it.toInteger()}
def absValueXY = Math.max(Math.abs(a[0]), Math.abs(a[1]))
def absValueZ = Math.abs(a[2])
log.debug "absValueXY: $absValueXY, absValueZ: $absValueZ"
if (absValueZ > 825 && absValueXY < 175) {
results << createEvent(name: "contact", value: "open", unit: "")
results << createEvent(name: "status", value: "open", unit: "")
log.debug "STATUS: open"
}
else if (absValueZ < 75 && absValueXY > 825) {
results << createEvent(name: "contact", value: "closed", unit: "")
results << createEvent(name: "status", value: "closed", unit: "")
log.debug "STATUS: closed"
}
results
}
private List parseRssiLqiMessage(String description) {
def results = []
// "lastHopRssi: 91, lastHopLqi: 255, rssi: 91, lqi: 255"
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('lastHopRssi:')) {
results << getRssiResult(part, description, true)
}
else if (part.startsWith('lastHopLqi:')) {
results << getLqiResult(part, description, true)
}
else if (part.startsWith('rssi:')) {
results << getRssiResult(part, description)
}
else if (part.startsWith('lqi:')) {
results << getLqiResult(part, description)
}
}
results
}
private getAccelerationResult(part, description) {
def name = "acceleration"
def value = part.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText ${name} was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: value,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
}
private getTempResult(part, description) {
def name = "temperature"
def temperatureScale = getTemperatureScale()
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value°$temperatureScale"
def isStateChange = isTemperatureStateChange(device, name, value.toString())
[
name: name,
value: value,
unit: temperatureScale,
linkText: linkText,
descriptionText: descriptionText,
handlerName: name,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
}
private getXyzResult(results, description) {
def name = "threeAxis"
def value = "${results.x},${results.y},${results.z}"
def linkText = getLinkText(device)
def descriptionText = "$linkText ${name} was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: name,
isStateChange: isStateChange,
displayed: false
]
}
private getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText ${name} was ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: name,
isStateChange: isStateChange,
displayed: false
]
}
private getRssiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopRssi" : "rssi"
def valueString = part.split(":")[1].trim()
def value = (Integer.parseInt(valueString) - 128).toString()
def linkText = getLinkText(device)
def descriptionText = "$linkText ${name} was $value dBm"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: "dBm",
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
displayed: false
]
}
/**
* Use LQI (Link Quality Indicator) as a measure of signal strength. The values
* are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal
* strength. Return as a percentage of 255.
*
* Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI.
*/
private getLqiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopLqi" : "lqi"
def valueString = part.split(":")[1].trim()
def percentageOf = 255
def value = Math.round((Integer.parseInt(valueString) / percentageOf * 100)).toString()
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText ${name} was: ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
displayed: false
]
}
private Boolean isAccelerationMessage(String description) {
// "acceleration: 1, rssi: 91, lqi: 255"
description ==~ /acceleration:.*rssi:.*lqi:.*/
}
private Boolean isContactMessage(String description) {
// "contactState: 1, accelerationState: 0, temp: 14.4 C, battery: 28, rssi: 59, lqi: 255"
description ==~ /contactState:.*accelerationState:.*temp:.*battery:.*rssi:.*lqi:.*/
}
private Boolean isRssiLqiMessage(String description) {
// "lastHopRssi: 91, lastHopLqi: 255, rssi: 91, lqi: 255"
description ==~ /lastHopRssi:.*lastHopLqi:.*rssi:.*lqi:.*/
}
private Boolean isOrientationMessage(String description) {
// "x: 0, y: 33, z: 1017, rssi: 102, lqi: 255"
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
}

View File

@@ -1,317 +1,317 @@
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at: * in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Smart Security * Smart Security
* *
* Author: SmartThings * Author: SmartThings
* Date: 2013-03-07 * Date: 2013-03-07
*/ */
definition( definition(
name: "Smart Security", name: "Smart Security",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Alerts you when there are intruders but not when you just got up for a glass of water in the middle of the night", description: "Alerts you when there are intruders but not when you just got up for a glass of water in the middle of the night",
category: "Safety & Security", category: "Safety & Security",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe@2x.png"
) )
preferences { preferences {
section("Sensors detecting an intruder") { section("Sensors detecting an intruder") {
input "intrusionMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false input "intrusionMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
input "intrusionContacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false input "intrusionContacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
} }
section("Sensors detecting residents") { section("Sensors detecting residents") {
input "residentMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false input "residentMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
} }
section("Alarm settings and actions") { section("Alarm settings and actions") {
input "alarms", "capability.alarm", title: "Which Alarm(s)", multiple: true, required: false input "alarms", "capability.alarm", title: "Which Alarm(s)", multiple: true, required: false
input "silent", "enum", options: ["Yes","No"], title: "Silent alarm only (Yes/No)" input "silent", "enum", options: ["Yes","No"], title: "Silent alarm only (Yes/No)"
input "seconds", "number", title: "Delay in seconds before siren sounds" input "seconds", "number", title: "Delay in seconds before siren sounds"
input "lights", "capability.switch", title: "Flash these lights (optional)", multiple: true, required: false input "lights", "capability.switch", title: "Flash these lights (optional)", multiple: true, required: false
input "newMode", "mode", title: "Change to this mode (optional)", required: false input "newMode", "mode", title: "Change to this mode (optional)", required: false
} }
section("Notify others (optional)") { section("Notify others (optional)") {
input "textMessage", "text", title: "Send this message", multiple: false, required: false input "textMessage", "text", title: "Send this message", multiple: false, required: false
input("recipients", "contact", title: "Send notifications to") { input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "To this phone", multiple: false, required: false input "phone", "phone", title: "To this phone", multiple: false, required: false
} }
} }
section("Arm system when residents quiet for (default 3 minutes)") { section("Arm system when residents quiet for (default 3 minutes)") {
input "residentsQuietThreshold", "number", title: "Time in minutes", required: false input "residentsQuietThreshold", "number", title: "Time in minutes", required: false
} }
} }
def installed() { def installed() {
log.debug "INSTALLED" log.debug "INSTALLED"
subscribeToEvents() subscribeToEvents()
state.alarmActive = null state.alarmActive = null
} }
def updated() { def updated() {
log.debug "UPDATED" log.debug "UPDATED"
unsubscribe() unsubscribe()
subscribeToEvents() subscribeToEvents()
unschedule() unschedule()
state.alarmActive = null state.alarmActive = null
state.residentsAreUp = null state.residentsAreUp = null
state.lastIntruderMotion = null state.lastIntruderMotion = null
alarms?.off() alarms?.off()
} }
private subscribeToEvents() private subscribeToEvents()
{ {
subscribe intrusionMotions, "motion", intruderMotion subscribe intrusionMotions, "motion", intruderMotion
subscribe residentMotions, "motion", residentMotion subscribe residentMotions, "motion", residentMotion
subscribe intrusionContacts, "contact", contact subscribe intrusionContacts, "contact", contact
subscribe alarms, "alarm", alarm subscribe alarms, "alarm", alarm
subscribe(app, appTouch) subscribe(app, appTouch)
} }
private residentsHaveBeenQuiet() private residentsHaveBeenQuiet()
{ {
def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000
def result = true def result = true
def t0 = new Date(now() - threshold) def t0 = new Date(now() - threshold)
for (sensor in residentMotions) { for (sensor in residentMotions) {
def recentStates = sensor.statesSince("motion", t0) def recentStates = sensor.statesSince("motion", t0)
if (recentStates.find{it.value == "active"}) { if (recentStates.find{it.value == "active"}) {
result = false result = false
break break
} }
} }
log.debug "residentsHaveBeenQuiet: $result" log.debug "residentsHaveBeenQuiet: $result"
result result
} }
private intruderMotionInactive() private intruderMotionInactive()
{ {
def result = true def result = true
for (sensor in intrusionMotions) { for (sensor in intrusionMotions) {
if (sensor.currentMotion == "active") { if (sensor.currentMotion == "active") {
result = false result = false
break break
} }
} }
result result
} }
private isResidentMotionSensor(evt) private isResidentMotionSensor(evt)
{ {
residentMotions?.find{it.id == evt.deviceId} != null residentMotions?.find{it.id == evt.deviceId} != null
} }
def appTouch(evt) def appTouch(evt)
{ {
alarms?.off() alarms?.off()
state.alarmActive = false state.alarmActive = false
} }
// Here to handle old subscriptions // Here to handle old subscriptions
def motion(evt) def motion(evt)
{ {
if (isResidentMotionSensor(evt)) { if (isResidentMotionSensor(evt)) {
log.debug "resident motion, $evt.name: $evt.value" log.debug "resident motion, $evt.name: $evt.value"
residentMotion(evt) residentMotion(evt)
} }
else { else {
log.debug "intruder motion, $evt.name: $evt.value" log.debug "intruder motion, $evt.name: $evt.value"
intruderMotion(evt) intruderMotion(evt)
} }
} }
def intruderMotion(evt) def intruderMotion(evt)
{ {
if (evt.value == "active") { if (evt.value == "active") {
log.debug "motion by potential intruder, residentsAreUp: $state.residentsAreUp" log.debug "motion by potential intruder, residentsAreUp: $state.residentsAreUp"
if (!state.residentsAreUp) { if (!state.residentsAreUp) {
log.trace "checking if residents have been quiet" log.trace "checking if residents have been quiet"
if (residentsHaveBeenQuiet()) { if (residentsHaveBeenQuiet()) {
log.trace "calling startAlarmSequence" log.trace "calling startAlarmSequence"
startAlarmSequence() startAlarmSequence()
} }
else { else {
log.trace "calling disarmIntrusionDetection" log.trace "calling disarmIntrusionDetection"
disarmIntrusionDetection() disarmIntrusionDetection()
} }
} }
} }
state.lastIntruderMotion = now() state.lastIntruderMotion = now()
} }
def residentMotion(evt) def residentMotion(evt)
{ {
// Don't think we need this any more // Don't think we need this any more
//if (evt.value == "inactive") { //if (evt.value == "inactive") {
// if (state.residentsAreUp) { // if (state.residentsAreUp) {
// startReArmSequence() // startReArmSequence()
// } // }
//} //}
} }
def contact(evt) def contact(evt)
{ {
if (evt.value == "open") { if (evt.value == "open") {
// TODO - check for residents being up? // TODO - check for residents being up?
if (!state.residentsAreUp) { if (!state.residentsAreUp) {
if (residentsHaveBeenQuiet()) { if (residentsHaveBeenQuiet()) {
startAlarmSequence() startAlarmSequence()
} }
else { else {
disarmIntrusionDetection() disarmIntrusionDetection()
} }
} }
} }
} }
def alarm(evt) def alarm(evt)
{ {
log.debug "$evt.name: $evt.value" log.debug "$evt.name: $evt.value"
if (evt.value == "off") { if (evt.value == "off") {
alarms?.off() alarms?.off()
state.alarmActive = false state.alarmActive = false
} }
} }
private disarmIntrusionDetection() private disarmIntrusionDetection()
{ {
log.debug "residents are up, disarming intrusion detection" log.debug "residents are up, disarming intrusion detection"
state.residentsAreUp = true state.residentsAreUp = true
scheduleReArmCheck() scheduleReArmCheck()
} }
private scheduleReArmCheck() private scheduleReArmCheck()
{ {
def cron = "0 * * * * ?" def cron = "0 * * * * ?"
schedule(cron, "checkForReArm") schedule(cron, "checkForReArm")
log.debug "Starting re-arm check, cron: $cron" log.debug "Starting re-arm check, cron: $cron"
} }
def checkForReArm() def checkForReArm()
{ {
def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000
log.debug "checkForReArm: threshold is $threshold" log.debug "checkForReArm: threshold is $threshold"
// check last intruder motion // check last intruder motion
def lastIntruderMotion = state.lastIntruderMotion def lastIntruderMotion = state.lastIntruderMotion
log.debug "checkForReArm: lastIntruderMotion=$lastIntruderMotion" log.debug "checkForReArm: lastIntruderMotion=$lastIntruderMotion"
if (lastIntruderMotion != null) if (lastIntruderMotion != null)
{ {
log.debug "checkForReArm, time since last intruder motion: ${now() - lastIntruderMotion}" log.debug "checkForReArm, time since last intruder motion: ${now() - lastIntruderMotion}"
if (now() - lastIntruderMotion > threshold) { if (now() - lastIntruderMotion > threshold) {
log.debug "re-arming intrusion detection" log.debug "re-arming intrusion detection"
state.residentsAreUp = false state.residentsAreUp = false
unschedule() unschedule()
} }
} }
else { else {
log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection" log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
} }
} }
private startAlarmSequence() private startAlarmSequence()
{ {
if (state.alarmActive) { if (state.alarmActive) {
log.debug "alarm already active" log.debug "alarm already active"
} }
else { else {
state.alarmActive = true state.alarmActive = true
log.debug "starting alarm sequence" log.debug "starting alarm sequence"
sendPush("Potential intruder detected!") sendPush("Potential intruder detected!")
if (newMode) { if (newMode) {
setLocationMode(newMode) setLocationMode(newMode)
} }
if (silentAlarm()) { if (silentAlarm()) {
log.debug "Silent alarm only" log.debug "Silent alarm only"
alarms?.strobe() alarms?.strobe()
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients)
} }
else { else {
if (phone) { if (phone) {
sendSms(phone, textMessage ?: "Potential intruder detected") sendSms(phone, textMessage ?: "Potential intruder detected")
} }
} }
} }
else { else {
def delayTime = seconds def delayTime = seconds
if (delayTime) { if (delayTime) {
alarms?.strobe() alarms?.strobe()
runIn(delayTime, "soundSiren") runIn(delayTime, "soundSiren")
log.debug "Sounding siren in $delayTime seconds" log.debug "Sounding siren in $delayTime seconds"
} }
else { else {
soundSiren() soundSiren()
} }
} }
if (lights) { if (lights) {
flashLights(Math.min((seconds/2) as Integer, 10)) flashLights(Math.min((seconds/2) as Integer, 10))
} }
} }
} }
def soundSiren() def soundSiren()
{ {
if (state.alarmActive) { if (state.alarmActive) {
log.debug "Sounding siren" log.debug "Sounding siren"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients)
} }
else { else {
if (phone) { if (phone) {
sendSms(phone, textMessage ?: "Potential intruder detected") sendSms(phone, textMessage ?: "Potential intruder detected")
} }
} }
alarms?.both() alarms?.both()
if (lights) { if (lights) {
log.debug "continue flashing lights" log.debug "continue flashing lights"
continueFlashing() continueFlashing()
} }
} }
else { else {
log.debug "alarm activation aborted" log.debug "alarm activation aborted"
} }
unschedule("soundSiren") // Temporary work-around to scheduling bug unschedule("soundSiren") // Temporary work-around to scheduling bug
} }
def continueFlashing() def continueFlashing()
{ {
unschedule() unschedule()
if (state.alarmActive) { if (state.alarmActive) {
flashLights(10) flashLights(10)
schedule(util.cronExpression(now() + 10000), "continueFlashing") schedule(util.cronExpression(now() + 10000), "continueFlashing")
} }
} }
private flashLights(numFlashes) { private flashLights(numFlashes) {
def onFor = 1000 def onFor = 1000
def offFor = 1000 def offFor = 1000
log.debug "FLASHING $numFlashes times" log.debug "FLASHING $numFlashes times"
def delay = 1L def delay = 1L
numFlashes.times { numFlashes.times {
log.trace "Switch on after $delay msec" log.trace "Switch on after $delay msec"
lights?.on(delay: delay) lights?.on(delay: delay)
delay += onFor delay += onFor
log.trace "Switch off after $delay msec" log.trace "Switch off after $delay msec"
lights?.off(delay: delay) lights?.off(delay: delay)
delay += offFor delay += offFor
} }
} }
private silentAlarm() private silentAlarm()
{ {
silent?.toLowerCase() in ["yes","true","y"] silent?.toLowerCase() in ["yes","true","y"]
} }