Compare commits

..

6 Commits

Author SHA1 Message Date
Brian Warner
55081dd486 MSA-726: I wrote this app to add some garage-door-specific smarts to my outside lighting. I wasn't able to find anything that was aware of multiple garage door sensors, and that would keep the lights on for a certain amount of time after the last door was closed. The use case is as follows:
A garage door goes up while it's dark.  All lights turn on and stay on as long as the door is open, which is useful if you're working outside at night.  If you were actually pulling a car out to leave, you'd want the lights on as you pull out of the driveway, so there is an option to keep them on for a certain time after the door closes.
2015-12-03 19:51:55 -06:00
Duncan McKee
90dee51255 Merge pull request #297 from mckeed/master
Z-Wave Lock: fix Security Exception DVCSMP-1265 DVCSMP-1059
2015-12-02 12:11:11 -06:00
Yaima
7b7fdd43cd Merge pull request #326 from Yaima/master
Implemented toggle() for locks
2015-12-01 10:52:29 -08:00
Yaima Valdivia
9059718818 Implemented toggle() for locks
https://smartthings.atlassian.net/browse/DVCSMP-672
2015-12-01 10:48:15 -08:00
Duncan McKee
8ae9b06022 Z-Wave Lock: fix double updated() commands DVCSMP-1265 2015-11-17 18:08:44 -05:00
Duncan McKee
71fc8e7f5f Fix Z-Wave Lock SecurityException DVCSMP-1265 2015-11-16 18:52:08 -05:00
4 changed files with 143 additions and 224 deletions

View File

@@ -66,9 +66,20 @@ metadata {
import physicalgraph.zwave.commands.doorlockv1.*
import physicalgraph.zwave.commands.usercodev1.*
def updated() {
try {
if (!state.init) {
state.init = true
response(secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()]))
}
} catch (e) {
log.warn "updated() threw $e"
}
}
def parse(String description) {
def result = null
if (description.startsWith("Err")) {
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
@@ -80,6 +91,8 @@ def parse(String description) {
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2, 0x85: 2, 0x86: 1 ])
if (cmd) {
@@ -286,7 +299,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
}
break
case 167:
if (!state.lastbatt || (new Date().time) - state.lastbatt > 12*60*60*1000) {
if (!state.lastbatt || now() - state.lastbatt > 12*60*60*1000) {
map = [ descriptionText: "$device.displayName: battery low", isStateChange: true ]
result << response(secure(zwave.batteryV1.batteryGet()))
} else {
@@ -431,7 +444,7 @@ def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
} else {
map.value = cmd.batteryLevel
}
state.lastbatt = new Date().time
state.lastbatt = now()
createEvent(map)
}
@@ -499,15 +512,14 @@ def refresh() {
cmds << "delay 4200"
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() // old Schlage locks use group 2 and don't secure the Association CC
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = new Date().time
} else if (new Date().time - state.associationQuery.toLong() > 9000) {
log.debug "setting association"
state.associationQuery = now()
} else if (secondsPast(state.associationQuery, 9)) {
cmds << "delay 6000"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = new Date().time
state.associationQuery = now()
}
log.debug "refresh sending ${cmds.inspect()}"
cmds
@@ -515,55 +527,22 @@ def refresh() {
def poll() {
def cmds = []
if (state.assoc != zwaveHubNodeId && secondsPast(state.associationQuery, 19 * 60)) {
log.debug "setting association"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << "delay 6000"
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
cmds << "delay 6000"
state.associationQuery = new Date().time
} else {
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = (new Date()).time
} else if (!state.MSR) {
cmds << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
} else if (!state.fw) {
cmds << zwave.versionV1.versionGet().format()
} else if (!state.codes) {
state.pollCode = 1
cmds << secure(zwave.userCodeV1.usersNumberGet())
} else if (state.pollCode && state.pollCode <= state.codes) {
cmds << requestCode(state.pollCode)
} else if (!state.lastbatt || (new Date().time) - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
} else if (!state.enc) {
encryptCodes()
state.enc = 1
}
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = now()
} else if (!state.lastbatt || now() - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
state.lastbatt = now() //inside-214
}
log.debug "poll is sending ${cmds.inspect()}"
device.activity()
cmds ?: null
}
private def encryptCodes() {
def keys = new ArrayList(state.keySet().findAll { it.startsWith("code") })
keys.each { key ->
def match = (key =~ /^code(\d+)$/)
if (match) try {
def keynum = match[0][1].toInteger()
if (keynum > 30 && !state[key]) {
state.remove(key)
} else if (state[key] && !state[key].startsWith("~")) {
log.debug "encrypting $key: ${state[key].inspect()}"
state[key] = encrypt(state[key])
}
} catch (java.lang.NumberFormatException e) { }
if (cmds) {
log.debug "poll is sending ${cmds.inspect()}"
cmds
} else {
// workaround to keep polling from stopping due to lack of activity
sendEvent(descriptionText: "skipping poll", isStateChange: true, displayed: false)
null
}
}
@@ -672,7 +651,7 @@ private Boolean secondsPast(timestamp, seconds) {
return true
}
}
return (new Date().time - timestamp) > (seconds * 1000)
return (now() - timestamp) > (seconds * 1000)
}
private allCodesDeleted() {

View File

@@ -0,0 +1,105 @@
/**
* Light Up The Night
*
* Copyright 2015 Brian Warner
*
* 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: "Light Up The Night",
namespace: "brianwarner",
author: "Brian Warner",
description: "Turn on certain lights when a door opens at night, and turn them off a certain time after the last door closes. Great for garage doors and driveway lights.",
category: "Convenience",
iconUrl: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn.png",
iconX2Url: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn@2x.png",
iconX3Url: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn@3x.png")
preferences {
section() {
input "contactsensors", "capability.contactSensor", multiple: true, title: "When these doors open:"
}
section() {
input "lights", "capability.switch", required: true, multiple: true, title: "Turn on these lights:"
}
section() {
input "timer", "number", required: true, title: "Keep them on until all doors are closed for this many minutes:", range: "0..240"
}
section() {
input "sunriseoffset", "number", required: true, title: "Start turning on the lights this many minutes before sunset:", range: "0..360"
}
section() {
input "sunsetoffset", "number", required: true, title: "Stop turning on the lights this many minutes after sunrise:", range: "0..360"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
subscribe(garagedoors, "door.open", doorOpenHandler)
subscribe(garagedoors, "door.closed", doorClosedHandler)
subscribe(contactsensors, "contact.open", doorOpenHandler)
subscribe(contactsensors, "contact.closed", doorClosedHandler)
}
def doorOpenHandler(evt) {
// log.debug "Door opened."
def now = new Date()
def sunTime = getSunriseAndSunset(sunriseOffset: "00:$sunriseoffset", sunsetOffset: "-00:$sunsetoffset")
if (now > sunTime.sunset || now < sunTime.sunrise) {
// log.debug "Turning lights on."
lights.on()
}
}
def doorClosedHandler(evt) {
// log.debug "A door closed."
runIn(60 * timer, checkClosed)
}
def checkClosed() {
// log.debug "Checking to ensure all doors are still closed."
def contactSensorState = contactsensors.currentState("contact")
def anyContactSensorsOpen = contactSensorState.value.findAll {it == "open"}
if (!anyContactSensorsOpen) {
def elapsed = now() - contactSensorState.date.time.max()
def timeout = 1000 * 60 * timer
if (elapsed >= timeout) {
// log.debug "Doors have stayed closed. Turning off the lights."
lights.off()
} else {
// log.debug "Doors were opened. Wait a little longer."
}
} else {
// log.debug "It appears a door is still open."
}
}

View File

@@ -246,6 +246,9 @@ def toggle(devices) {
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}

View File

@@ -1,168 +0,0 @@
/**
* Auto Lock
*
* Copyright 2015 Vikash Varma
*
* 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.
*
*/
// Automatically generated. Make future change here.
definition(
name: "Auto Lock",
namespace: "varma",
author: "Vikash Varma",
description: "Manage authorized users and send notification when lock is unlocked. Automatically lock it based on elapsed time or no motion detected near the lock",
category: "Safety & Security",
iconUrl: "http://ecx.images-amazon.com/images/I/51lIeDU229L._SL1500_.jpg",
iconX2Url: "http://ecx.images-amazon.com/images/I/51lIeDU229L._SL1500_.jpg",
oauth: true
)
import groovy.json.JsonSlurper
preferences {
page (name: "mainPage", title: "Automatically locks a deadbolt or lever lock using motion sensor or time elapsed", nextPage: "page2", uninstall: true) {
section {
input "lockDevice", "capability.lock", title:"Which Lock?"
input "lockOpt", "enum", title: "Preference", metadata: [values: ["Motion", "Time"]], multiple:false
input "maxusers", "number", title: "Maximum authorized users", required:true
input "pushNotify", "boolean" , title:"Send push notification when unlocked", default: true
}
}
page (name: "page2", nextPage: "adduser", title: "Automatically locks a deadbolt or lever lock using motion sensor or time elapsed", install: true, uninstall: true)
}
def page2() {
dynamicPage(name: "page2") {
section("Set $lockOpt Preference") {
if (lockOpt == "Motion" ) {
input "motion1", "capability.motionSensor", title:"Lock door after no motion"
} else {
input "minutesLater", "number", title: "Lock door after how many minutes?"
}
}
for (int i = 1; i <= settings.maxusers; i++) {
section("Add User #${i}") {
input "user${i}", "string", title: "Username", required:true
input "code${i}", "password", title: "Code", required:true
}
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
state.lockStatus = 1 // 0 = locked; 1 = unlocked
subscribe(lockDevice, "lock.locked", lockEvent)
if (lockOpt == 'Time') {
subscribe(lockDevice, "lock.unlocked", unlockEvent)
} else {
subscribe(motion1, "motion", motionHandler)
}
deleteLockCode()
state.maxusers = 0
for (int i=0; i< settings.maxusers; i++) {
runIn(i*180, setLockCode, [overwrite: false])
}
}
def setLockCode() {
log.trace "setLockCode start: state.maxuser=$state.maxusers"
if (state.maxusers < settings.maxusers ) {
state.maxusers = state.maxusers + 1
def lockCode = settings."code${state.maxusers}"
lockCode = lockCode +""
def msg = "$lockDevice added user $state.maxusers, code: $lockCode"
log.info msg
lockDevice.setCode(state.maxusers, lockCode)
} else {
log.debug "end scheduling,state.maxuser=$state.maxusers, settings.maxusers=$settings.maxusers"
}
log.trace "setLockCode end: state.maxuser=$state.maxusers"
}
def unlockEvent(evt) {
log.debug "Lock ${lockDevice} was: ${evt.value}"
state.lockStatus = 1
def delay = minutesLater * 60
log.debug "Locking $lockDevice.displayName in ${minutesLater} minutes"
runIn(delay, lockDoor)
def data = []
def unlockmsg = ""
if (evt.data != null) {
data = new JsonSlurper().parseText(evt.data)
if (data.usedCode <= settings.maxusers) {
def u = settings."user${data.usedCode}"
unlockmsg = "$lockDevice was unlocked by $u"
} else {
unlockmsg = "$lockDevice was unlocked by unknown user"
}
} else {
unlockmsg = "$lockDevice was unlocked by app"
}
log.debug "pushNotify=$pushNotify | unlockmsg=$unlockmsg"
if (pushNotify) {
sendPush(unlockmsg);
} else {
log.debug "noPush"
}
}
def lockEvent(evt) {
state.lockStatus = 0
log.debug "$lockDevice.displayName is locked"
unschedule( lockDoor )
}
def motionHandler(evt) {
if (evt.value == "inactive") {
lockDoor(evt)
}
}
def lockDoor(evt) {
if ( state.lockStatus ) {
sendPush("$app.name is locking $lockDevice.displayName")
lockDevice.lock()
state.lockStatus = 0
} else {
log.debug "$lolockDeviceck1.displayName already locked"
}
}
def deleteLockCode() {
log.debug "in deleteLockCode"
for (int i = settings.maxusers + 1 ; i <= state.maxusers; i++) {
lockDevice.deleteCode(i)
log.debug "Deleting code $i"
}
}
def checkCodeSetup() {
}