Compare commits

..

17 Commits

Author SHA1 Message Date
박춘영
2a871b66b4 MSA-934: 스마트싱작업 테스트해보는중입니다 2016-03-08 03:23:23 -06:00
Vinay Rao
e83d08cf2f Merge pull request #597 from workingmonk/bug/cree_bulb
[DVCSMP-1442] Cree bulb does not properly reflect dimming value
2016-03-07 20:17:53 -08:00
Vinay Rao
55905a10da issue with CREE Bulb not reporting back state
updating copyright and spacing
2016-03-07 19:13:19 -08:00
Juan Pablo Risso
546ee007f1 Merge pull request #561 from juano2310/hue-PROB-528
PROB-528 - Commented singleInstance: true
2016-03-07 17:27:37 -08:00
Juan Pablo Risso
21ae20302c Merge pull request #584 from juano2310/Hue_ColorTemp
DVCSMP-1565 & DVCSMP-1548
2016-03-07 17:27:18 -08:00
juano2310
9880ced851 Fix wrong DT for Hue bulb 2016-03-07 17:05:06 -08:00
juano2310
fb99a81704 fixed rich-control 2016-03-04 17:15:49 -05:00
juano2310
6bda59c340 DVCSMP-1565 & DVCSMP-1548
DVCSMP-1565 Color for light is not adjusted.
DVCSMP-1548 Color temperature
2016-03-04 17:08:24 -05:00
Vinay Rao
c1422438ac Merge pull request #576 from workingmonk/bug/refresh_tile
fix device.refresh for refresh tiles
2016-03-03 14:18:13 -08:00
Yaima
8ed23f4c7e Merge pull request #577 from Yaima/master
Including unknown temperature values as part of the response for sensors
2016-03-02 14:24:04 -08:00
Yaima Valdivia
e7e6ea7d56 Including unknown temperature values as part of the response for sensors
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-03-02 14:20:44 -08:00
Vinay Rao
12896f4095 device.refresh change for tile 2016-03-01 19:51:27 -08:00
Vinay Rao
ab4e8a892a Merge pull request #572 from workingmonk/bug/battery_values
[DVCSMP-1255] Fixing issue with weird battery values
2016-03-01 17:01:27 -08:00
Vinay Rao
e076818573 Merge pull request #573 from workingmonk/deprecate_DTH
[DVCSMP-1463] Deprecating copied DTH
2016-03-01 16:57:50 -08:00
Vinay Rao
cd8bbca5ee removing the fingerprints from the additional copy of DTH 2016-03-01 12:46:16 -08:00
Vinay Rao
2d060bddfc fixing issue with weird battery values 2016-03-01 12:17:50 -08:00
Juan Pablo Risso
600a9a2ca1 PROB-528 - Commented singleInstance: true
This will allow any one with more than one Hue Bridge to install an instance for each Bridge.
2016-02-26 14:45:12 -05:00
21 changed files with 608 additions and 513 deletions

View File

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

View File

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

View File

@@ -48,8 +48,8 @@ metadata {
}
standardTile("motion", "device.motion") {
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:"#53a7c0")
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {

View File

@@ -1,4 +1,3 @@
/**
* Hue Bulb
*
@@ -11,13 +10,14 @@ metadata {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
command "reset"
command "refresh"
}
simulator {
@@ -25,7 +25,7 @@ metadata {
}
tiles (scale: 2){
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
multiAttributeTile(name:"rich-control", 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 "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
@@ -33,23 +33,58 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
state "hue", label: 'Hue ${currentValue} '
}
main(["switch"])
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
main(["switch"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
// parse events into attributes
@@ -119,19 +154,27 @@ void setColor(value) {
void reset() {
log.debug "Executing 'reset'"
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value)
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
}
}

View File

@@ -9,51 +9,59 @@ metadata {
definition (name: "Hue Lux Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "refresh"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", 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 "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 "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", 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 "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 "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
}
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
main(["switch"])
details(["rich-control", "refresh"])
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
// parse events into attributes
@@ -90,6 +98,14 @@ void setLevel(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() {
log.debug "Executing 'refresh'"
parent.manualRefresh()

View File

@@ -14,6 +14,8 @@
*
*/
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
@@ -25,10 +27,6 @@ metadata {
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 {
@@ -233,7 +231,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {}
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."

View File

@@ -13,6 +13,7 @@
* 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 {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -23,8 +24,7 @@
capability "Refresh"
capability "Temperature Measurement"
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 {
@@ -225,7 +225,8 @@ def getTemperature(value) {
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {

View File

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

View File

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

View File

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

View File

@@ -39,7 +39,7 @@ metadata {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
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) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:2) {
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) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}

View File

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

View File

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

View File

@@ -1,65 +0,0 @@
/**
* Controlled Power Off
*
* Copyright 2016 Andrew Crow
*
* 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.
*
*/
definition(
name: "Controlled Power Off",
namespace: "acrow311",
author: "Andrew Crow",
description: "Application used to power off devices that should be allowed to finish their cycle before being shut down such as air conditions and tankless water heaters. Application will monitor electric usage and delay shutdown until usage has returned to inactive state.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: true)
preferences {
section("Shutdown when device not active") {
input(name: "meter", type: "capability.powerMeter", title: "When This Power Meter...", required: true, multiple: false, description: null)
input(name: "threshold", type: "number", title: "Reports Below...", required: true, description: "In watts, enter integer value")
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
initialize()
}
def initialize() {
unsubscribe()
subscribe(meter, "power", meterHandler)
}
def meterHandler(evt) {
def meterValue = evt.value as double
def thresholdValue = threshold as int
def switchState = meter.currentValue("switch") == "on" // Get current switch status (on = true)
if (switchState) { // If the switch is already off, do nothing
if (meterValue < thresholdValue) { // If the power consumption is low enough, turn off switch
log.info "${meter} reported energy ${meterValue} below ${threshold}. Turning off switch."
sendNotificationEvent("${meter} not running, shutting down.")
meter.off()
} else { // Power consumption too high - device connected to switch in use - do not shutdown.
sendNotificationEvent("Power consumption too high to shut off ${meter}. Attempting again in a moment.")
log.info "${meter} reported energy ${meterValue} above ${threshold}. Leaving switch on."
}
}
}

View File

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

View File

@@ -15,7 +15,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Hue (Connect)",
namespace: "smartthings",
@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true
//singleInstance: true
)
preferences {
@@ -58,7 +58,7 @@ def bridgeDiscovery(params=[:])
state.bridges = [:]
state.bridgeRefreshCount = 0
app.updateSetting("selectedHue", "")
}
}
subscribe(location, null, locationHandler, [filterEvents:false])
@@ -130,8 +130,8 @@ def bulbDiscovery() {
def bulboptions = bulbsDiscovered() ?: [:]
def numFound = bulboptions.size() ?: 0
if (numFound == 0)
app.updateSetting("selectedBulbs", "")
app.updateSetting("selectedBulbs", "")
if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs()
}
@@ -140,7 +140,7 @@ def bulbDiscovery() {
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
}
section {
section {
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
@@ -246,13 +246,13 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unsubscribe()
unschedule()
unsubscribe()
unschedule()
initialize()
}
def initialize() {
log.debug "Initializing"
log.debug "Initializing"
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
@@ -281,18 +281,18 @@ def uninstalled(){
def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:]
if (state.inBulbDiscovery) {
if (state.inBulbDiscovery) {
def logg = ""
log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data)
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
}
}
}
def bridge = null
if (selectedHue)
if (selectedHue)
bridge = getChildDevice(selectedHue)
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
msg = "${bulbs.size()} bulbs found. ${bulbs}"
@@ -318,7 +318,7 @@ def addBulbs() {
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
} else {
} else {
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
@@ -344,7 +344,7 @@ def addBridge() {
def d = getChildDevice(selectedHue)
if(!d) {
// compatibility with old devices
def newbridge = true
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
@@ -354,10 +354,10 @@ def addBridge() {
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue)
app.updateSetting("selectedHue", newDNI)
newbridge = false
newbridge = false
}
}
}
}
}
if (newbridge) {
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
@@ -368,13 +368,13 @@ def addBridge() {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
} else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
}
}
} else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
}
}
}
} else {
log.debug "found ${d.displayName} with id $selectedHue already exists"
@@ -436,7 +436,7 @@ def locationHandler(evt) {
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
}
}
}
@@ -504,11 +504,11 @@ def isValidSource(macAddress) {
/////////////////////////////////////
def parse(childDevice, description) {
def parsedEvent = parseLanMessage(description)
def parsedEvent = parseLanMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = parsedEvent.headers.toString()
def bodyString = parsedEvent.body.toString()
if (headerString?.contains("json")) {
if (headerString?.contains("json")) {
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
@@ -516,11 +516,11 @@ def parse(childDevice, description) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.HashMap) {
if (body instanceof java.util.HashMap) {
//poll response
def bulbs = getChildDevices()
for (bulb in body) {
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (d) {
if (bulb.value.state?.reachable) {
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
@@ -535,18 +535,18 @@ def parse(childDevice, description) {
}
} else {
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
if (bulb.value.state.sat) {
def hue = 23
def sat = 56
def hex = colorUtil.hslToHex(23, 56)
sendEvent(d.deviceNetworkId, [name: "color", value: hex])
sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
}
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
}
}
}
}
}
}
else
{ //put response
@@ -595,7 +595,7 @@ def parse(childDevice, description) {
}
}
}
}
} else {
log.debug "parse - got something other than headers,body..."
return []
@@ -616,7 +616,7 @@ def off(childDevice) {
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def level
def level
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
}
@@ -633,6 +633,14 @@ def setHue(childDevice, percent) {
put("lights/${getId(childDevice)}/state", [hue: level])
}
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654))
def value = [ct: ct, on: true]
log.trace "sending command $value"
put("lights/${getId(childDevice)}/state", value)
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
@@ -689,7 +697,7 @@ HOST: ${host}
}
private put(path, body) {
def host = getBridgeIP()
def host = getBridgeIP()
def uri = "/api/${state.username}/$path"
def bodyJSON = new groovy.json.JsonBuilder(body).toString()
def length = bodyJSON.getBytes().size().toString()
@@ -715,11 +723,11 @@ private getBridgeIP() {
host = d.getDeviceDataByName("networkAddress")
else
host = d.latestState('networkAddress').stringValue
}
}
if (host == null || host == "") {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
@@ -729,9 +737,9 @@ private getBridgeIP() {
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
}
}
log.trace "Bridge: $selectedHue - Host: $host"
}
}
return host
}

View File

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