Compare commits

..

9 Commits

Author SHA1 Message Date
Jason Botello 7669bec0bc Update timevalve-smart.groovy
Commenting out fingerprint temporarily to avoid potential conflicts with other devices as this devices is specifically for a Korean deployment in AP01 - see DVCSMP-1425
2016-01-20 16:17:32 -08:00
Lee Joonmin 3a7abd6169 Modifying 'Timevalve Smart' 2016-01-12 18:01:23 -06:00
Lee Joonmin 7def620f04 MSA-757: Smart Gas-Lock work with z-wave 2015-12-23 03:44:19 -06:00
Umang Parekh 5b0b239caa Merge pull request #386 from umangparekh/multiSensorThresholdFix
Fine-tuning the Motion Threshold and Motion Threshold multiplier. Garage use case works. XYZ values reported correctly.
2015-12-22 19:58:59 -08:00
umangparekh 3ea70fecad Fine-tuning the Motion Threshold and Motion Threshold multiplier for manufacturer Smarthings.
Garage Door use case works + implemented code review feedback

Implementing code review feedback + inverted the signedY to match Centralite like behavior.

Implementing code review feedback + inverted the signedY to match Centralite like behavior.
2015-12-22 19:33:06 -08:00
Tyler Lange f420907043 Merge pull request #381 from kris-schaller/master
Adding paths to locally hosted images for bose sound control
2015-12-21 12:53:45 -08:00
Kris Schaller bf915b49dc Adding paths to locally hosted images for bose sound control 2015-12-21 12:48:17 -08:00
Tyler Lange 1c2a65e313 Merge pull request #380 from kris-schaller/master
Adding icons to Bose Sound Touch
2015-12-21 12:40:14 -08:00
Kris Schaller 075fdf0974 Adding icons to Bose Sound Touch 2015-12-21 12:38:17 -08:00
7 changed files with 425 additions and 400 deletions
@@ -371,21 +371,50 @@ def getTemperature(value) {
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = [
/* sensitivity - default value (8) */
def refreshCmds = []
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (D200)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {D200}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse()
}
@@ -461,19 +490,34 @@ private Map parseAxis(String description) {
xyzResults.x = signedX
log.debug "X Part: ${signedX}"
}
// Y and the Z axes are interchanged between SmartThings's implementation and Centralite's implementation
else if (part.startsWith("13")) {
def unsignedY = hexToInt(part.split("13")[1].trim())
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
xyzResults.y = signedY
log.debug "Y Part: ${signedY}"
if (device.getDataValue("manufacturer") == "SmartThings") {
xyzResults.z = -signedY
log.debug "Z Part: ${xyzResults.z}"
if (garageSensor == "Yes")
garageEvent(xyzResults.z)
}
else {
xyzResults.y = signedY
log.debug "Y Part: ${signedY}"
}
}
else if (part.startsWith("14")) {
def unsignedZ = hexToInt(part.split("14")[1].trim())
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
xyzResults.z = signedZ
log.debug "Z Part: ${signedZ}"
if (garageSensor == "Yes")
garageEvent(signedZ)
if (device.getDataValue("manufacturer") == "SmartThings") {
xyzResults.y = signedZ
log.debug "Y Part: ${signedZ}"
} else {
xyzResults.z = signedZ
log.debug "Z Part: ${signedZ}"
if (garageSensor == "Yes")
garageEvent(signedZ)
}
}
}
@@ -553,3 +597,4 @@ private byte[] reverseArray(byte[] array) {
return array
}
@@ -0,0 +1,243 @@
metadata {
definition (name: "Timevalve Smart", namespace: "timevalve.gaslock.t-08", author: "ruinnel") {
capability "Valve"
capability "Refresh"
capability "Battery"
capability "Temperature Measurement"
command "setRemaining"
command "setTimeout"
command "setTimeout10"
command "setTimeout20"
command "setTimeout30"
command "setTimeout40"
command "remainingLevel"
attribute "remaining", "number"
attribute "remainingText", "String"
attribute "timeout", "number"
//raw desc : 0 0 0x1006 0 0 0 7 0x5E 0x86 0x72 0x5A 0x73 0x98 0x80
//fingerprint deviceId:"0x1006", inClusters:"0x5E, 0x86, 0x72, 0x5A, 0x73, 0x98, 0x80"
}
tiles (scale: 2) {
multiAttributeTile(name:"statusTile", type:"generic", width:6, height:4) {
tileAttribute("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "close", icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', action: "", icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
tileAttribute("device.remainingText", key: "SECONDARY_CONTROL") {
attributeState "open", label: '${currentValue}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
standardTile("refreshTile", "command.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("remainingSliderTile", "device.remaining", "slider", inactiveLabel: false, range:"(0..590)", height: 2, width: 4) {
state "level", action:"remainingLevel"
}
valueTile("setRemaining", "device.remainingText", inactiveLabel: false, decoration: "flat", height: 2, width: 2){
state "remainingText", label:'${currentValue}\nRemaining'//, action: "setRemaining"//, icon: "st.Office.office6"
}
standardTile("setTimeout10", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'10Min', action: "setTimeout10", icon:"st.Health & Wellness.health7", defaultState: true
state "10", label:'10Min', action: "setTimeout10", icon:"st.Office.office13"
}
standardTile("setTimeout20", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'20Min', action: "setTimeout20", icon:"st.Health & Wellness.health7", defaultState: true
state "20", label:'20Min', action: "setTimeout20", icon:"st.Office.office13"
}
standardTile("setTimeout30", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'30Min', action: "setTimeout30", icon:"st.Health & Wellness.health7", defaultState: true
state "30", label:'30Min', action: "setTimeout30", icon:"st.Office.office13"
}
standardTile("setTimeout40", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'40Min', action: "setTimeout40", icon:"st.Health & Wellness.health7", defaultState: true
state "40", label:'40Min', action: "setTimeout40", icon:"st.Office.office13"
}
valueTile("batteryTile", "device.battery", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["statusTile"])
// details (["statusTile", "remainingSliderTile", "setRemaining", "setTimeout10", "setTimeout20", "batteryTile", "refreshTile", "setTimeout30", "setTimeout40"])
// details (["statusTile", "batteryTile", "setRemaining", "refreshTile"])
details (["statusTile", "batteryTile", "refreshTile"])
}
}
def parse(description) {
// log.debug "parse - " + description
def result = null
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent(descriptionText: description, isStateChange: true)
} else if (description != "updated") {
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
if (cmd) {
log.debug "parsed cmd = " + cmd
result = zwaveEvent(cmd)
//log.debug("'$description' parsed to $result")
} else {
log.debug("Couldn't zwave.parse '$description'")
}
}
return result
}
// 복호화 후 zwaveEvent() 호출
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
//log.debug "SecurityMessageEncapsulation - " + cmd
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
if (encapsulatedCommand) {
state.sec = 1
log.debug "encapsulatedCommand = " + encapsulatedCommand
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
//log.debug "switch status - " + cmd.value
createEvent(name:"contact", value: cmd.value ? "open" : "closed")
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) { // Special value for low battery alert
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
log.debug "battery - ${map.value}${map.unit}"
// Store time of last battery update so we don't ask every wakeup, see WakeUpNotification handler
state.lastbatt = new Date().time
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
//log.debug "zwaveEvent - ${device.displayName}: ${cmd}"
createEvent(descriptionText: "${device.displayName}: ${cmd}")
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def result = []
log.info "zwave.configurationV1.configurationGet - " + cmd
def array = cmd.configurationValue
def value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
if (device.currentValue("contact") == "open") {
value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
} else {
value = 0
}
if (device.currentValue('contact') == 'open') {
def hour = value.intdiv(60);
def min = (value % 60).toString().padLeft(2, '0');
def text = "${hour}:${min}M"
log.info "remain - " + text
result.add( createEvent(name: "remaining", value: value, displayed: false, isStateChange: true) )
result.add( createEvent(name: "remainingText", value: text, displayed: false, isStateChange: true) )
} else {
result.add( createEvent(name: "timeout", value: value, displayed: false, isStateChange: true) )
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def type = cmd.notificationType
if (type == cmd.NOTIFICATION_TYPE_HEAT) {
log.info "NotificationReport - ${type}"
createEvent(name: "temperature", value: 999, unit: "C", descriptionText: "${device.displayName} is over heat!", displayed: true, isStateChange: true)
}
}
def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd) {
def type = cmd.alarmType
def level = cmd.alarmLevel
log.info "AlarmReport - type : ${type}, level : ${level}"
def msg = "${device.displayName} is over heat!"
def result = createEvent(name: "temperature", value: 999, unit: "C", descriptionText: msg, displayed: true, isStateChange: true)
if (sendPushMessage) {
sendPushMessage(msg)
}
return result
}
// remote open not allow
def open() {}
def close() {
// log.debug 'cmd - close()'
commands([
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00),
zwave.switchBinaryV1.switchBinaryGet()
])
}
def setTimeout10() { setTimeout(10) }
def setTimeout20() { setTimeout(20) }
def setTimeout30() { setTimeout(30) }
def setTimeout40() { setTimeout(40) }
def setTimeout(value) {
// log.debug "setDefaultTime($value)"
commands([
zwave.configurationV1.configurationSet(parameterNumber: 0x01, size: 4, scaledConfigurationValue: value * 60),
zwave.configurationV1.configurationGet(parameterNumber: 0x01)
]);
}
def remainingLevel(value) {
// log.debug "remainingLevel($value)"
def hour = value.intdiv(60);
def min = (value % 60).toString().padLeft(2, '0');
def text = "${hour}:${min}M"
sendEvent(name: "remaining", value: value, displayed: false, isStateChange: true)
sendEvent(name: "remainingText", value: text, displayed: false, isStateChange: true)
}
def setRemaining() {
def remaining = device.currentValue("remaining")
// log.debug "setConfiguration() - remaining : $remaining"
commands([
zwave.configurationV1.configurationSet(parameterNumber: 0x03, size: 4, scaledConfigurationValue: remaining * 60),
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
]);
}
private command(physicalgraph.zwave.Command cmd) {
if (state.sec != 0 && !(cmd instanceof physicalgraph.zwave.commands.batteryv1.BatteryGet)) {
log.debug "cmd = " + cmd + ", encapsulation"
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
log.debug "cmd = " + cmd + ", plain"
cmd.format()
}
}
private commands(commands, delay=200) {
delayBetween(commands.collect{ command(it) }, delay)
}
def refresh() {
// log.debug 'cmd - refresh()'
commands([
zwave.batteryV1.batteryGet(),
zwave.switchBinaryV1.switchBinaryGet(),
zwave.configurationV1.configurationGet(parameterNumber: 0x01),
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
], 400)
}
@@ -1,427 +1,164 @@
/**
* Nobody Home
*
* Author: brian@bevey.org, raychi@gmail.com
* Date: 12/02/2015
* Author: brian@bevey.org
* Date: 12/19/14
*
* 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.
* Monitors a set of presence detectors and triggers a mode change when everyone has left.
* When everyone has left, sets mode to a new defined mode.
* When at least one person returns home, set the mode back to a new defined mode.
* When someone is home - or upon entering the home, their mode may change dependent on sunrise / sunset.
*/
/**
* Monitors a set of presence sensors and trigger appropriate mode
* based on configured modes and sunrise/sunset time.
*
* - When everyone is away [Away]
* - When someone is home during the day [Home]
* - When someone is home at the night [Night]
*/
// ********** App related functions **********
// The definition provides metadata about the App to SmartThings.
definition (
name: "Nobody Home",
namespace: "imbrianj",
author: "brian@bevey.org",
description: "Automatically set Away/Home/Night mode based on a set of presence sensors and sunrise/sunset time.",
category: "Mode Magic",
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"
definition(
name: "Nobody Home",
namespace: "imbrianj",
author: "brian@bevey.org",
description: "When everyone leaves, change mode. If at least one person home, switch mode based on sun position.",
category: "Mode Magic",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
)
// The preferences defines information the App needs from the user.
preferences {
section("Presence sensors to monitor") {
input "people", "capability.presenceSensor", multiple: true
}
section("When all of these people leave home") {
input "people", "capability.presenceSensor", multiple: true
}
section("Mode setting") {
input "newAwayMode", "mode", title: "Everyone is away"
input "newSunriseMode", "mode", title: "Someone is home during the day"
input "newSunsetMode", "mode", title: "Someone is home at night"
}
section("Change to this mode to...") {
input "newAwayMode", "mode", title: "Everyone is away"
input "newSunsetMode", "mode", title: "At least one person home and nightfall"
input "newSunriseMode", "mode", title: "At least one person home and sunrise"
}
section("Mode change delay (minutes)") {
input "awayThreshold", "decimal", title: "Away delay [5m]", required: false
input "arrivalThreshold", "decimal", title: "Arrival delay [2m]", required: false
}
section("Away threshold (defaults to 10 min)") {
input "awayThreshold", "decimal", title: "Number of minutes", required: false
}
section("Notifications") {
input "sendPushMessage", "bool", title: "Push notification", required:false
}
section("Notifications") {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
}
}
// called when the user installs the App
def installed()
{
log.debug("installed() @${location.name}: ${settings}")
initialize(true)
def installed() {
init()
}
// called when the user installs the app, or changes the App
// preference
def updated()
{
log.debug("updated() @${location.name}: ${settings}")
unsubscribe()
initialize(false)
def updated() {
unsubscribe()
init()
}
def initialize(isInstall)
{
// subscribe to all the events we care about
log.debug("Subscribing to events ...")
def init() {
subscribe(people, "presence", presence)
subscribe(location, "sunrise", setSunrise)
subscribe(location, "sunset", setSunset)
// thing to subscribe, attribute/state we care about, and callback fn
subscribe(people, "presence", presenceHandler)
subscribe(location, "sunrise", sunriseHandler)
subscribe(location, "sunset", sunsetHandler)
// set the optional parameter values. these are not available
// directly until the app has initialized (that is,
// installed/updated has returned). so here we access them through
// the settings object, as otherwise will get an exception.
// store information we need in state object so we can get access
// to it later in our event handlers.
// calculate the away threshold in seconds. can't use the simpler
// default falsy value, as value of 0 (no delay) is evaluated to
// false (not specified), but we want 0 to represent no delay. so
// we compare against null explicitly to see if the user has set a
// value or not.
if (settings.awayThreshold == null) {
settings.awayThreshold = 5 // default away 5 minute
}
state.awayDelay = (int) settings.awayThreshold * 60
log.debug("awayThreshold set to " + state.awayDelay + " second(s)")
if (settings.arrivalThreshold == null) {
settings.arrivalThreshold = 2 // default arrival 2 minute
}
state.arrivalDelay = (int) settings.arrivalThreshold * 60
log.debug("arrivalThreshold set to " + state.arrivalDelay + " second(s)")
// get push notification setting
state.isPush = settings.sendPushMessage ? true : false
log.debug("sendPushMessage set to " + state.isPush)
// on install (not update), figure out what mode we should be in
// IF someone's home. This value is needed so that when a presence
// sensor is triggered, we know what mode to set the system to, as
// the sunrise/sunset event handler may not be triggered yet after
// a fresh install.
if (isInstall) {
// TODO: for now, we simply assume daytime. a better approach
// would be to figure out whether current time is day or
// night, and set it appropriately. However there
// doesn't seem to be a way to query this directly
// without a zip code. This will become the correct
// value at the next sunrise/sunset event.
log.debug("No sun info yet, assuming daytime")
state.modeIfHome = newSunriseMode
// set keep a separate sun mode state so we can show a more
// helpful message when sun events are triggered.
state.currentSunMode = "sunUnknown"
state.eventDevice = "" // last event device
// device that triggered timer. This is not necessarily the
// eventDevice. For example, if A arrives, kick off timer,
// then b arrives before timer elapsed, we want the
// notification message to reference A, not B.
state.timerDevice = null
// anything in flight? We use this to avoid scheduling
// duplicate timers (so we don't extend the timer).
state.pendingOp = "init"
// now set the correct mode for the location. This way, we
// don't need to wait for the next sun/presence event.
// we schedule this action to run after the app has fully
// initialized. This way, the app install is faster and the
// user customized app name is used in the notification.
runIn(7, "setInitialMode")
}
// On update, we don't change state.modeIfHome. This is so that we
// preserve the current sun rise/set state we obtained in earlier
// sunset/sunrise handler. This way the app remains in the correct
// sun state when the user reconfigures it.
state.sunMode = location.mode
}
def setInitialMode()
{
changeSunMode(state.modeIfHome)
state.pendingOp = null
def setSunrise(evt) {
changeSunMode(newSunriseMode)
}
// ********** sunrise/sunset handling **********
// event handler when the sunrise time is reached
def sunriseHandler(evt)
{
// we store the mode we should be in, IF someone's home
state.modeIfHome = newSunriseMode
state.currentSunMode = "sunRise"
// change mode if someone's home, otherwise set to away
changeSunMode(newSunriseMode)
def setSunset(evt) {
changeSunMode(newSunsetMode)
}
// event handler when the sunset time is reached
def sunsetHandler(evt)
{
// we store the mode we should be in, IF someone's home
state.modeIfHome = newSunsetMode
state.currentSunMode = "sunSet"
def changeSunMode(newMode) {
state.sunMode = newMode
// change mode if someone's home, otherwise set to away
changeSunMode(newSunsetMode)
if(everyoneIsAway() && (location.mode == newAwayMode)) {
log.debug("Mode is away, not evaluating")
}
else if(location.mode != newMode) {
def message = "${app.label} changed your mode to '${newMode}'"
send(message)
setLocationMode(newMode)
}
else {
log.debug("Mode is the same, not evaluating")
}
}
def changeSunMode(newMode)
{
// if everyone is away, we need to check and ensure the system is
// in away mode.
if (isEveryoneAway()) {
// this shouldn not happen normally as the mode should already
// be changed during presenceHandler, but in case this is not
// done, such as when app is initially installed while away,
// and system is not in away mode, then we toggle it to away
// at the sun rise/set event.
changeMode(newAwayMode, " because no one is present")
} else {
// someone is home, we update the mode depending on
// sunrise/sunset.
if (state.currentSunMode == "sunRise") {
changeMode(newMode, " because it's sunrise")
} else if (state.currentSunMode == "sunSet") {
changeMode(newMode, " because it's sunset")
} else {
changeMode(newMode)
}
def presence(evt) {
if(evt.value == "not present") {
log.debug("Checking if everyone is away")
if(everyoneIsAway()) {
log.info("Starting ${newAwayMode} sequence")
def delay = (awayThreshold != null && awayThreshold != "") ? awayThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
}
else {
if(location.mode != state.sunMode) {
log.debug("Checking if anyone is home")
if(anyoneIsHome()) {
log.info("Starting ${state.sunMode} sequence")
changeSunMode(state.sunMode)
}
}
else {
log.debug("Mode is the same, not evaluating")
}
}
}
// ********** presence handling **********
// event handler when presence sensor changes state
def presenceHandler(evt)
{
// get the device name that resulted in the change
state.eventDevice= evt.device?.displayName
// is setInitialMode() still pending?
if (state.pendingOp == "init") {
log.debug("Pending ${state.pendingOp} op still in progress, ignoring presence event")
return
def setAway() {
if(everyoneIsAway()) {
if(location.mode != newAwayMode) {
def message = "${app.label} changed your mode to '${newAwayMode}' because everyone left home"
log.info(message)
send(message)
setLocationMode(newAwayMode)
}
if (evt.value == "not present") {
handleDeparture()
} else {
handleArrival()
else {
log.debug("Mode is the same, not evaluating")
}
}
else {
log.info("Somebody returned home before we set to '${newAwayMode}'")
}
}
def handleDeparture()
{
log.info("${state.eventDevice} left ${location.name}")
private everyoneIsAway() {
def result = true
// do nothing if someone's still home
if (!isEveryoneAway()) {
log.info("Someone is still present, no actions needed")
return
}
if(people.findAll { it?.currentPresence == "present" }) {
result = false
}
// Now we set away mode. We perform the following actions even if
// home is already in away mode because an arrival timer may be
// pending, and scheduling delaySetMode() has the nice effect of
// canceling any previous pending timer, which is what we want to
// do. So we do this even if delay is 0.
log.info("Scheduling ${newAwayMode} mode in " + state.awayDelay + "s")
state.pendingOp = "away"
state.timerDevice = state.eventDevice
// we always use runIn(). This has the benefit of automatically
// replacing any pending arrival/away timer. if any arrival timer
// is active, it will be clobbered with this away timer. If any
// away timer is active, it will be extended with this new timeout
// (though normally it should not happen)
runIn(state.awayDelay, "delaySetMode")
log.debug("everyoneIsAway: ${result}")
return result
}
def handleArrival()
{
// someone returned home, set home/night mode after delay
log.info("${state.eventDevice} arrived at ${location.name}")
private anyoneIsHome() {
def result = false
def numHome = isAnyoneHome()
if (!numHome) {
// no one home, do nothing for now (should NOT happen)
log.warn("${deviceName} arrived, but isAnyoneHome() returned false!")
return
}
if(people.findAll { it?.currentPresence == "present" }) {
result = true
}
if (numHome > 1) {
// not the first one home, do nothing, as any action that
// should happen would've happened when the first sensor
// arrived. this is the opposite of isEveryoneAway() where we
// don't do anything if someone's still home.
log.debug("Someone is already present, no actions needed")
return
}
log.debug("anyoneIsHome: ${result}")
// check if any pending arrival timer is already active. we want
// the timer to trigger when the 1st person arrives, but not
// extended when the 2nd person arrives later. this should not
// happen because of the >1 check above, but just in case.
if (state.pendingOp == "arrive") {
log.debug("Pending ${state.pendingOp} op already in progress, do nothing")
return
}
// now we set home/night mode
log.info("Scheduling ${state.modeIfHome} mode in " + state.arrivalDelay + "s")
state.pendingOp = "arrive"
state.timerDevice = state.eventDevice
// if any away timer is active, it will be clobbered with
// this arrival timer
runIn(state.arrivalDelay, "delaySetMode")
return result
}
private send(msg) {
if(sendPushMessage != "No") {
log.debug("Sending push message")
sendPush(msg)
}
// ********** helper functions **********
// change the system to the new mode, unless its already in that mode.
def changeMode(newMode, reason="")
{
if (location.mode != newMode) {
// notification message
def message = "${location.name} changed mode from '${location.mode}' to '${newMode}'" + reason
setLocationMode(newMode)
send(message) // send message after changing mode
} else {
log.debug("${location.name} is already in ${newMode} mode, no actions needed")
}
log.debug(msg)
}
// create a useful departure/arrival reason string
def reasonStr(isAway, delaySec, delayMin)
{
def reason
// if we are invoked by timer, use the stored timer trigger
// device, otherwise use the last event device
if (state.timerDevice) {
reason = " because ${state.timerDevice} "
} else {
reason = " because ${state.eventDevice} "
}
if (isAway) {
reason += "left"
} else {
reason += "arrived"
}
if (delaySec) {
if (delaySec > 60) {
if (delayMin == null) {
delayMin = (int) delaySec / 60
}
reason += " ${delayMin} minutes ago"
} else {
reason += " ${delaySec}s ago"
}
}
return reason
}
// http://docs.smartthings.com/en/latest/smartapp-developers-guide/scheduling.html#schedule-from-now
//
// By default, if a method is scheduled to run in the future, and then
// another call to runIn with the same method is made, the last one
// overwrites the previously scheduled method.
//
// We use the above property to schedule our arrval/departure delay
// using the same function so we don't have to worry about
// arrival/departure timer firing independently and complicating code.
def delaySetMode()
{
def newMode = null
def reason = ""
// timer has elapsed, check presence status to figure out what we
// need to do
if (isEveryoneAway()) {
reason = reasonStr(true, state.awayDelay, awayThreshold)
newMode = newAwayMode
if (state.pendingOp) {
log.debug("${state.pendingOp} timer elapsed: everyone is away")
}
} else {
reason = reasonStr(false, state.arrivalDelay, arrivalThreshold)
newMode = state.modeIfHome
if (state.pendingOp) {
log.debug("${state.pendingOp} timer elapsed: someone is home")
}
}
// now change the mode
changeMode(newMode, reason);
state.pendingOp = null
state.timerDevice = null
}
private isEveryoneAway()
{
def result = true
if (people.findAll { it?.currentPresence == "present" }) {
result = false
}
// log.debug("isEveryoneAway: ${result}")
return result
}
// return the number of people that are home
private isAnyoneHome()
{
def result = 0
// iterate over our people variable that we defined
// in the preferences method
for (person in people) {
if (person.currentPresence == "present") {
result++
}
}
return result
}
private send(msg)
{
if (state.isPush) {
log.debug("Sending push notification")
sendPush(msg)
} else {
log.debug("Sending notification")
sendNotificationEvent(msg)
}
log.info(msg)
}
@@ -22,9 +22,9 @@ definition(
author: "SmartThings & Joe Geiger",
description: "Control your Bose® SoundTouch® when certain actions take place in your home.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png"
)
preferences {
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB