2 * Copyright 2015 SmartThings
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11 * for the specific language governing permissions and limitations under the License.
19 name: "Smart Security",
20 namespace: "smartthings",
21 author: "SmartThings",
22 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",
23 category: "Safety & Security",
24 iconUrl: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe.png",
25 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe@2x.png"
29 section("Sensors detecting an intruder") {
30 input "intrusionMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
31 input "intrusionContacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
33 section("Sensors detecting residents") {
34 input "residentMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
36 section("Alarm settings and actions") {
37 input "alarms", "capability.alarm", title: "Which Alarm(s)", multiple: true, required: false
38 input "silent", "enum", options: ["Yes","No"], title: "Silent alarm only (Yes/No)"
39 input "seconds", "number", title: "Delay in seconds before siren sounds"
40 input "lights", "capability.switch", title: "Flash these lights (optional)", multiple: true, required: false
41 input "newMode", "mode", title: "Change to this mode (optional)", required: false
43 section("Notify others (optional)") {
44 input "textMessage", "text", title: "Send this message", multiple: false, required: false
45 input("recipients", "contact", title: "Send notifications to") {
46 input "phone", "phone", title: "To this phone", multiple: false, required: false
49 section("Arm system when residents quiet for (default 3 minutes)") {
50 input "residentsQuietThreshold", "number", title: "Time in minutes", required: false
57 state.alarmActive = null
65 state.alarmActive = null
66 state.residentsAreUp = null
67 state.lastIntruderMotion = null
71 private subscribeToEvents()
73 subscribe intrusionMotions, "motion", intruderMotion
74 // subscribe residentMotions, "motion", residentMotion
75 subscribe intrusionContacts, "contact", contact
76 subscribe alarms, "alarm", alarm
77 subscribe(app, appTouch)
80 private residentsHaveBeenQuiet()
82 def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000
84 def t0 = new Date(now() - threshold)
85 for (sensor in residentMotions) {
86 def recentStates = sensor.statesSince("motion", t0)
87 if (recentStates.find{it.value == "active"}) {
92 log.debug "residentsHaveBeenQuiet: $result"
96 private intruderMotionInactive()
99 for (sensor in intrusionMotions) {
100 if (sensor.currentMotion == "active") {
108 private isResidentMotionSensor(evt)
110 residentMotions?.find{it.id == evt.deviceId} != null
116 state.alarmActive = false
119 // Here to handle old subscriptions
122 if (isResidentMotionSensor(evt)) {
123 log.debug "resident motion, $evt.name: $evt.value"
127 log.debug "intruder motion, $evt.name: $evt.value"
132 def intruderMotion(evt)
134 if (evt.value == "active") {
135 log.debug "motion by potential intruder, residentsAreUp: $state.residentsAreUp"
136 if (!state.residentsAreUp) {
137 log.trace "checking if residents have been quiet"
138 if (residentsHaveBeenQuiet()) {
139 log.trace "calling startAlarmSequence"
143 log.trace "calling disarmIntrusionDetection"
144 disarmIntrusionDetection()
148 state.lastIntruderMotion = now()
151 def residentMotion(evt)
153 // Don't think we need this any more
154 //if (evt.value == "inactive") {
155 // if (state.residentsAreUp) {
156 // startReArmSequence()
159 unsubscribe(residentMotions)
164 if (evt.value == "open") {
165 // TODO - check for residents being up?
166 if (!state.residentsAreUp) {
167 if (residentsHaveBeenQuiet()) {
171 disarmIntrusionDetection()
179 log.debug "$evt.name: $evt.value"
180 if (evt.value == "off") {
182 state.alarmActive = false
186 private disarmIntrusionDetection()
188 log.debug "residents are up, disarming intrusion detection"
189 state.residentsAreUp = true
193 private scheduleReArmCheck()
195 def cron = "0 * * * * ?"
196 schedule(cron, "checkForReArm")
197 log.debug "Starting re-arm check, cron: $cron"
202 def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000
203 log.debug "checkForReArm: threshold is $threshold"
204 // check last intruder motion
205 def lastIntruderMotion = state.lastIntruderMotion
206 log.debug "checkForReArm: lastIntruderMotion=$lastIntruderMotion"
207 if (lastIntruderMotion != null)
209 log.debug "checkForReArm, time since last intruder motion: ${now() - lastIntruderMotion}"
210 if (now() - lastIntruderMotion > threshold) {
211 log.debug "re-arming intrusion detection"
212 state.residentsAreUp = false
217 log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
221 private startAlarmSequence()
223 if (state.alarmActive) {
224 log.debug "alarm already active"
227 state.alarmActive = true
228 log.debug "starting alarm sequence"
230 sendPush("Potential intruder detected!")
233 setLocationMode(newMode)
237 log.debug "Silent alarm only"
239 if (location.contactBookEnabled) {
240 sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients)
244 sendSms(phone, textMessage ?: "Potential intruder detected")
249 def delayTime = seconds
252 runIn(delayTime, "soundSiren")
253 log.debug "Sounding siren in $delayTime seconds"
261 flashLights(Math.min((seconds/2) as Integer, 10))
268 if (state.alarmActive) {
269 log.debug "Sounding siren"
270 if (location.contactBookEnabled) {
271 sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients)
275 sendSms(phone, textMessage ?: "Potential intruder detected")
280 log.debug "continue flashing lights"
285 log.debug "alarm activation aborted"
287 unschedule("soundSiren") // Temporary work-around to scheduling bug
290 def continueFlashing()
293 if (state.alarmActive) {
295 schedule(util.cronExpression(now() + 10000), "continueFlashing")
299 private flashLights(numFlashes) {
303 log.debug "FLASHING $numFlashes times"
306 log.trace "Switch on after $delay msec"
307 lights?.on(delay: delay)
309 log.trace "Switch off after $delay msec"
310 lights?.off(delay: delay)
315 private silentAlarm()
317 silent?.toLowerCase() in ["yes","true","y"]