From a051c8ba958cdeb8a198e6e6a85bcaa84a0ba144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=B6=98=EC=98=81?= Date: Tue, 8 Mar 2016 03:23:17 -0600 Subject: [PATCH] =?UTF-8?q?MSA-933:=20=EC=8A=A4=EB=A7=88=ED=8A=B8=EC=8B=B1?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=95=B4?= =?UTF-8?q?=EB=B3=B4=EB=8A=94=EC=A4=91=EC=9E=85=EB=8B=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devicetypes/-/test.src/test.groovy | 93 +++ .../smart-security.src/smart-security.groovy | 634 +++++++++--------- 2 files changed, 410 insertions(+), 317 deletions(-) create mode 100644 devicetypes/-/test.src/test.groovy diff --git a/devicetypes/-/test.src/test.groovy b/devicetypes/-/test.src/test.groovy new file mode 100644 index 0000000..1ab4df4 --- /dev/null +++ b/devicetypes/-/test.src/test.groovy @@ -0,0 +1,93 @@ +/** + * 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 +} \ No newline at end of file diff --git a/smartapps/smartthings/smart-security.src/smart-security.groovy b/smartapps/smartthings/smart-security.src/smart-security.groovy index 6d15cff..c3eddb2 100644 --- a/smartapps/smartthings/smart-security.src/smart-security.groovy +++ b/smartapps/smartthings/smart-security.src/smart-security.groovy @@ -1,317 +1,317 @@ -/** - * 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. - * - * Smart Security - * - * Author: SmartThings - * Date: 2013-03-07 - */ -definition( - name: "Smart Security", - namespace: "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", - category: "Safety & Security", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe@2x.png" -) - -preferences { - section("Sensors detecting an intruder") { - input "intrusionMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false - input "intrusionContacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false - } - section("Sensors detecting residents") { - input "residentMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false - } - section("Alarm settings and actions") { - 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 "seconds", "number", title: "Delay in seconds before siren sounds" - input "lights", "capability.switch", title: "Flash these lights (optional)", multiple: true, required: false - input "newMode", "mode", title: "Change to this mode (optional)", required: false - } - section("Notify others (optional)") { - input "textMessage", "text", title: "Send this message", multiple: false, required: false - input("recipients", "contact", title: "Send notifications to") { - input "phone", "phone", title: "To this phone", multiple: false, required: false - } - } - section("Arm system when residents quiet for (default 3 minutes)") { - input "residentsQuietThreshold", "number", title: "Time in minutes", required: false - } -} - -def installed() { - log.debug "INSTALLED" - subscribeToEvents() - state.alarmActive = null -} - -def updated() { - log.debug "UPDATED" - unsubscribe() - subscribeToEvents() - unschedule() - state.alarmActive = null - state.residentsAreUp = null - state.lastIntruderMotion = null - alarms?.off() -} - -private subscribeToEvents() -{ - subscribe intrusionMotions, "motion", intruderMotion - subscribe residentMotions, "motion", residentMotion - subscribe intrusionContacts, "contact", contact - subscribe alarms, "alarm", alarm - subscribe(app, appTouch) -} - -private residentsHaveBeenQuiet() -{ - def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 - def result = true - def t0 = new Date(now() - threshold) - for (sensor in residentMotions) { - def recentStates = sensor.statesSince("motion", t0) - if (recentStates.find{it.value == "active"}) { - result = false - break - } - } - log.debug "residentsHaveBeenQuiet: $result" - result -} - -private intruderMotionInactive() -{ - def result = true - for (sensor in intrusionMotions) { - if (sensor.currentMotion == "active") { - result = false - break - } - } - result -} - -private isResidentMotionSensor(evt) -{ - residentMotions?.find{it.id == evt.deviceId} != null -} - -def appTouch(evt) -{ - alarms?.off() - state.alarmActive = false -} - -// Here to handle old subscriptions -def motion(evt) -{ - if (isResidentMotionSensor(evt)) { - log.debug "resident motion, $evt.name: $evt.value" - residentMotion(evt) - } - else { - log.debug "intruder motion, $evt.name: $evt.value" - intruderMotion(evt) - } -} - -def intruderMotion(evt) -{ - if (evt.value == "active") { - log.debug "motion by potential intruder, residentsAreUp: $state.residentsAreUp" - if (!state.residentsAreUp) { - log.trace "checking if residents have been quiet" - if (residentsHaveBeenQuiet()) { - log.trace "calling startAlarmSequence" - startAlarmSequence() - } - else { - log.trace "calling disarmIntrusionDetection" - disarmIntrusionDetection() - } - } - } - state.lastIntruderMotion = now() -} - -def residentMotion(evt) -{ - // Don't think we need this any more - //if (evt.value == "inactive") { - // if (state.residentsAreUp) { - // startReArmSequence() - // } - //} -} - -def contact(evt) -{ - if (evt.value == "open") { - // TODO - check for residents being up? - if (!state.residentsAreUp) { - if (residentsHaveBeenQuiet()) { - startAlarmSequence() - } - else { - disarmIntrusionDetection() - } - } - } -} - -def alarm(evt) -{ - log.debug "$evt.name: $evt.value" - if (evt.value == "off") { - alarms?.off() - state.alarmActive = false - } -} - -private disarmIntrusionDetection() -{ - log.debug "residents are up, disarming intrusion detection" - state.residentsAreUp = true - scheduleReArmCheck() -} - -private scheduleReArmCheck() -{ - def cron = "0 * * * * ?" - schedule(cron, "checkForReArm") - log.debug "Starting re-arm check, cron: $cron" -} - -def checkForReArm() -{ - def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 - log.debug "checkForReArm: threshold is $threshold" - // check last intruder motion - def lastIntruderMotion = state.lastIntruderMotion - log.debug "checkForReArm: lastIntruderMotion=$lastIntruderMotion" - if (lastIntruderMotion != null) - { - log.debug "checkForReArm, time since last intruder motion: ${now() - lastIntruderMotion}" - if (now() - lastIntruderMotion > threshold) { - log.debug "re-arming intrusion detection" - state.residentsAreUp = false - unschedule() - } - } - else { - log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection" - } -} - -private startAlarmSequence() -{ - if (state.alarmActive) { - log.debug "alarm already active" - } - else { - state.alarmActive = true - log.debug "starting alarm sequence" - - sendPush("Potential intruder detected!") - - if (newMode) { - setLocationMode(newMode) - } - - if (silentAlarm()) { - log.debug "Silent alarm only" - alarms?.strobe() - if (location.contactBookEnabled) { - sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) - } - else { - if (phone) { - sendSms(phone, textMessage ?: "Potential intruder detected") - } - } - } - else { - def delayTime = seconds - if (delayTime) { - alarms?.strobe() - runIn(delayTime, "soundSiren") - log.debug "Sounding siren in $delayTime seconds" - } - else { - soundSiren() - } - } - - if (lights) { - flashLights(Math.min((seconds/2) as Integer, 10)) - } - } -} - -def soundSiren() -{ - if (state.alarmActive) { - log.debug "Sounding siren" - if (location.contactBookEnabled) { - sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) - } - else { - if (phone) { - sendSms(phone, textMessage ?: "Potential intruder detected") - } - } - alarms?.both() - if (lights) { - log.debug "continue flashing lights" - continueFlashing() - } - } - else { - log.debug "alarm activation aborted" - } - unschedule("soundSiren") // Temporary work-around to scheduling bug -} - -def continueFlashing() -{ - unschedule() - if (state.alarmActive) { - flashLights(10) - schedule(util.cronExpression(now() + 10000), "continueFlashing") - } -} - -private flashLights(numFlashes) { - def onFor = 1000 - def offFor = 1000 - - log.debug "FLASHING $numFlashes times" - def delay = 1L - numFlashes.times { - log.trace "Switch on after $delay msec" - lights?.on(delay: delay) - delay += onFor - log.trace "Switch off after $delay msec" - lights?.off(delay: delay) - delay += offFor - } -} - -private silentAlarm() -{ - silent?.toLowerCase() in ["yes","true","y"] -} +/** + * 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. + * + * Smart Security + * + * Author: SmartThings + * Date: 2013-03-07 + */ +definition( + name: "Smart Security", + namespace: "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", + category: "Safety & Security", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/SafetyAndSecurity/App-IsItSafe@2x.png" +) + +preferences { + section("Sensors detecting an intruder") { + input "intrusionMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false + input "intrusionContacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false + } + section("Sensors detecting residents") { + input "residentMotions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false + } + section("Alarm settings and actions") { + 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 "seconds", "number", title: "Delay in seconds before siren sounds" + input "lights", "capability.switch", title: "Flash these lights (optional)", multiple: true, required: false + input "newMode", "mode", title: "Change to this mode (optional)", required: false + } + section("Notify others (optional)") { + input "textMessage", "text", title: "Send this message", multiple: false, required: false + input("recipients", "contact", title: "Send notifications to") { + input "phone", "phone", title: "To this phone", multiple: false, required: false + } + } + section("Arm system when residents quiet for (default 3 minutes)") { + input "residentsQuietThreshold", "number", title: "Time in minutes", required: false + } +} + +def installed() { + log.debug "INSTALLED" + subscribeToEvents() + state.alarmActive = null +} + +def updated() { + log.debug "UPDATED" + unsubscribe() + subscribeToEvents() + unschedule() + state.alarmActive = null + state.residentsAreUp = null + state.lastIntruderMotion = null + alarms?.off() +} + +private subscribeToEvents() +{ + subscribe intrusionMotions, "motion", intruderMotion + subscribe residentMotions, "motion", residentMotion + subscribe intrusionContacts, "contact", contact + subscribe alarms, "alarm", alarm + subscribe(app, appTouch) +} + +private residentsHaveBeenQuiet() +{ + def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 + def result = true + def t0 = new Date(now() - threshold) + for (sensor in residentMotions) { + def recentStates = sensor.statesSince("motion", t0) + if (recentStates.find{it.value == "active"}) { + result = false + break + } + } + log.debug "residentsHaveBeenQuiet: $result" + result +} + +private intruderMotionInactive() +{ + def result = true + for (sensor in intrusionMotions) { + if (sensor.currentMotion == "active") { + result = false + break + } + } + result +} + +private isResidentMotionSensor(evt) +{ + residentMotions?.find{it.id == evt.deviceId} != null +} + +def appTouch(evt) +{ + alarms?.off() + state.alarmActive = false +} + +// Here to handle old subscriptions +def motion(evt) +{ + if (isResidentMotionSensor(evt)) { + log.debug "resident motion, $evt.name: $evt.value" + residentMotion(evt) + } + else { + log.debug "intruder motion, $evt.name: $evt.value" + intruderMotion(evt) + } +} + +def intruderMotion(evt) +{ + if (evt.value == "active") { + log.debug "motion by potential intruder, residentsAreUp: $state.residentsAreUp" + if (!state.residentsAreUp) { + log.trace "checking if residents have been quiet" + if (residentsHaveBeenQuiet()) { + log.trace "calling startAlarmSequence" + startAlarmSequence() + } + else { + log.trace "calling disarmIntrusionDetection" + disarmIntrusionDetection() + } + } + } + state.lastIntruderMotion = now() +} + +def residentMotion(evt) +{ + // Don't think we need this any more + //if (evt.value == "inactive") { + // if (state.residentsAreUp) { + // startReArmSequence() + // } + //} +} + +def contact(evt) +{ + if (evt.value == "open") { + // TODO - check for residents being up? + if (!state.residentsAreUp) { + if (residentsHaveBeenQuiet()) { + startAlarmSequence() + } + else { + disarmIntrusionDetection() + } + } + } +} + +def alarm(evt) +{ + log.debug "$evt.name: $evt.value" + if (evt.value == "off") { + alarms?.off() + state.alarmActive = false + } +} + +private disarmIntrusionDetection() +{ + log.debug "residents are up, disarming intrusion detection" + state.residentsAreUp = true + scheduleReArmCheck() +} + +private scheduleReArmCheck() +{ + def cron = "0 * * * * ?" + schedule(cron, "checkForReArm") + log.debug "Starting re-arm check, cron: $cron" +} + +def checkForReArm() +{ + def threshold = ((residentsQuietThreshold != null && residentsQuietThreshold != "") ? residentsQuietThreshold : 3) * 60 * 1000 + log.debug "checkForReArm: threshold is $threshold" + // check last intruder motion + def lastIntruderMotion = state.lastIntruderMotion + log.debug "checkForReArm: lastIntruderMotion=$lastIntruderMotion" + if (lastIntruderMotion != null) + { + log.debug "checkForReArm, time since last intruder motion: ${now() - lastIntruderMotion}" + if (now() - lastIntruderMotion > threshold) { + log.debug "re-arming intrusion detection" + state.residentsAreUp = false + unschedule() + } + } + else { + log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection" + } +} + +private startAlarmSequence() +{ + if (state.alarmActive) { + log.debug "alarm already active" + } + else { + state.alarmActive = true + log.debug "starting alarm sequence" + + sendPush("Potential intruder detected!") + + if (newMode) { + setLocationMode(newMode) + } + + if (silentAlarm()) { + log.debug "Silent alarm only" + alarms?.strobe() + if (location.contactBookEnabled) { + sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) + } + else { + if (phone) { + sendSms(phone, textMessage ?: "Potential intruder detected") + } + } + } + else { + def delayTime = seconds + if (delayTime) { + alarms?.strobe() + runIn(delayTime, "soundSiren") + log.debug "Sounding siren in $delayTime seconds" + } + else { + soundSiren() + } + } + + if (lights) { + flashLights(Math.min((seconds/2) as Integer, 10)) + } + } +} + +def soundSiren() +{ + if (state.alarmActive) { + log.debug "Sounding siren" + if (location.contactBookEnabled) { + sendNotificationToContacts(textMessage ?: "Potential intruder detected", recipients) + } + else { + if (phone) { + sendSms(phone, textMessage ?: "Potential intruder detected") + } + } + alarms?.both() + if (lights) { + log.debug "continue flashing lights" + continueFlashing() + } + } + else { + log.debug "alarm activation aborted" + } + unschedule("soundSiren") // Temporary work-around to scheduling bug +} + +def continueFlashing() +{ + unschedule() + if (state.alarmActive) { + flashLights(10) + schedule(util.cronExpression(now() + 10000), "continueFlashing") + } +} + +private flashLights(numFlashes) { + def onFor = 1000 + def offFor = 1000 + + log.debug "FLASHING $numFlashes times" + def delay = 1L + numFlashes.times { + log.trace "Switch on after $delay msec" + lights?.on(delay: delay) + delay += onFor + log.trace "Switch off after $delay msec" + lights?.off(delay: delay) + delay += offFor + } +} + +private silentAlarm() +{ + silent?.toLowerCase() in ["yes","true","y"] +} \ No newline at end of file