Compare commits

..

8 Commits

Author SHA1 Message Date
Vinay Rao
62a965d90b Merge pull request #599 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-10 Release
2016-03-10 16:26:26 -08:00
Vinay Rao
fb9f1dee47 Merge pull request #626 from munds/hue-hotfix
Hue hotfix for color temperature and overall stability
2016-03-10 15:04:01 -08:00
Amol Mundayoor
3a433d3865 Hue hotfix for color temperature and overall stability 2016-03-10 14:21:49 -08:00
Vinay Rao
8f25ff4434 Merge pull request #570 from SmartThingsCommunity/master
Rolling up changes to staging from master
2016-03-01 12:01:26 -08:00
Vinay Rao
515b268374 Merge pull request #549 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-02-25 Release
2016-02-26 10:51:42 -08:00
Vinay Rao
9b87d39fe8 Merge pull request #546 from SmartThingsCommunity/master
Rolling up changes from master to staging
2016-02-23 10:50:20 -08:00
Vinay Rao
a103d437c2 Merge pull request #523 from SmartThingsCommunity/staging
Deploy to production 2/18
2016-02-18 09:28:02 -08:00
Vinay Rao
bdd88deb99 Merge pull request #504 from SmartThingsCommunity/staging
Merging changes from staging to production
2016-02-11 22:40:38 -08:00
18 changed files with 369 additions and 478 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

@@ -1,7 +1,7 @@
/** /**
* Cree Bulb * Cree Bulb
* *
* Copyright 2016 SmartThings * Copyright 2014 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:
@@ -15,29 +15,29 @@
*/ */
metadata { metadata {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") { definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
} }
// simulator metadata // simulator metadata
simulator { simulator {
// status messages // status messages
status "on": "on/off: 1" status "on": "on/off: 1"
status "off": "on/off: 0" status "off": "on/off: 0"
// reply messages // reply messages
reply "zcl on-off on": "on/off: 1" reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0" reply "zcl on-off off": "on/off: 0"
} }
// UI tile definitions // UI tile definitions
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -62,12 +62,18 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getKnownDescription(description)
if (resultMap) { if (resultMap) {
sendEvent(resultMap) log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
} }
else { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description) log.debug zigbee.parseDescriptionAsMap(description)
} }
} }
@@ -81,7 +87,7 @@ def on() {
} }
def setLevel(value) { def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report zigbee.setLevel(value)
} }
def refresh() { def refresh() {

View File

@@ -48,8 +48,8 @@ metadata {
} }
standardTile("motion", "device.motion") { standardTile("motion", "device.motion") {
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0") state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {

View File

@@ -9,7 +9,6 @@ metadata {
definition (name: "Hue Lux Bulb", namespace: "smartthings", author: "SmartThings") { definition (name: "Hue Lux Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level" capability "Switch Level"
capability "Actuator" capability "Actuator"
capability "Color Temperature"
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
@@ -48,19 +47,12 @@ metadata {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["switch"]) main(["switch"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"]) details(["rich-control", "refresh"])
} }
} }
@@ -98,14 +90,6 @@ void setLevel(percent) {
sendEvent(name: "level", value: percent) sendEvent(name: "level", value: percent)
} }
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
}
}
void refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()

View File

@@ -14,8 +14,6 @@
* *
*/ */
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata { metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor" capability "Motion Sensor"
@@ -27,6 +25,10 @@ metadata {
command "enrollResponse" command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
} }
simulator { simulator {
@@ -231,7 +233,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."

View File

@@ -13,7 +13,6 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata { metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +23,8 @@
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
command "enrollResponse" command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
} }
simulator { simulator {
@@ -225,8 +225,7 @@ def getTemperature(value) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -220,8 +220,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -196,8 +196,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -44,7 +44,7 @@ metadata {
attributeState "power", label:'${currentValue} W' attributeState "power", label:'${currentValue} W'
} }
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -39,7 +39,7 @@ metadata {
attributeState "level", action:"switch level.setLevel" attributeState "level", action:"switch level.setLevel"
} }
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -63,7 +63,7 @@ metadata {
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {

View File

@@ -52,7 +52,7 @@
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) { valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:"" state "battery", label:'${currentValue}% battery', unit:""
} }
standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:2) { standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

@@ -57,7 +57,7 @@ metadata {
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorTemperature", label: '${currentValue} K'
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

@@ -40,7 +40,7 @@ metadata {
attributeState "power", label:'${currentValue} W' attributeState "power", label:'${currentValue} W'
} }
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -42,7 +42,7 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
} }
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -54,7 +54,7 @@ metadata {
} }
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

@@ -545,15 +545,10 @@ def updateSensorData() {
def occupancy = "" def occupancy = ""
it.capability.each { it.capability.each {
if (it.type == "temperature") { if (it.type == "temperature") {
if (it.value == "unknown") { if (location.temperatureScale == "F") {
temperature = "--" temperature = Math.round(it.value.toDouble() / 10)
} else { } else {
if (location.temperatureScale == "F") { temperature = convertFtoC(it.value.toDouble() / 10)
temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
} }
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {

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