diff --git a/devicetypes/ethayer/zwave-lock.src/zwave-lock-reporting.groovy b/devicetypes/ethayer/zwave-lock.src/zwave-lock-reporting.groovy index ff7a604..2e8815f 100644 --- a/devicetypes/ethayer/zwave-lock.src/zwave-lock-reporting.groovy +++ b/devicetypes/ethayer/zwave-lock.src/zwave-lock-reporting.groovy @@ -669,7 +669,6 @@ private allCodesDeleted() { } def reportAllCodes(state) { - log.debug 'report..' def map = [ name: "reportAllCodes", data: [:], displayed: false, isStateChange: false, type: "physical" ] state.each { entry -> //iterate through all the state entries and add them to the event data to be handled by application event handlers diff --git a/smartapps/ethayer/lock-manager.src/lock-manager.groovy b/smartapps/ethayer/lock-manager.src/lock-manager.groovy index b437037..91c9031 100755 --- a/smartapps/ethayer/lock-manager.src/lock-manager.groovy +++ b/smartapps/ethayer/lock-manager.src/lock-manager.groovy @@ -3,7 +3,7 @@ definition( namespace: 'ethayer', author: 'Erik Thayer', description: 'Manage locks and users', - category: 'My Apps', + category: 'Safety & Security', 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' @@ -13,6 +13,8 @@ import groovy.json.JsonBuilder preferences { page(name: 'mainPage', title: 'Users', install: true, uninstall: true,submitOnChange: true) + page(name: "lockInfoPage") + page(name: "infoRefreshPage") } def mainPage() { @@ -20,11 +22,66 @@ def mainPage() { section('Create') { app(name: 'lockUsers', appName: "Lock User", namespace: "ethayer", title: "New User", multiple: true) } - section('Which Locks?') { + section('Locks') { + if (locks) { + def i = 0 + locks.each { lock-> + i++ + href(name: "toLockInfoPage${i}", page: "lockInfoPage", params: [id: lock.id], required: false, title: lock.displayName ) + } + } + } + section('Global Settings') { input 'locks', 'capability.lockCodes', title: 'Select Locks', required: true, multiple: true, submitOnChange: true + input(name: "overwriteMode", title: "Overwrite?", type: "bool", required: true, defaultValue: true, description: 'Overwrite mode automatically deletes codes not in the users list') + href(name: "toInfoRefreshPage", page: "infoRefreshPage", title: "Refresh Lock Data", description: 'Tap to refresh') } } +} +def infoRefreshPage() { + dynamicPage(name:"infoRefreshPage", title:"Lock Info") { + section() { + doPoll() + paragraph "Lock info refreshing soon." + href(name: "toMainPage", page: "mainPage", title: "Back") + } + } +} + +def lockInfoPage(params) { + dynamicPage(name:"lockInfoPage", title:"Lock Info") { + def lock = getLock(params) + if (lock) { + section("${lock.displayName}") { + if (state."lock${lock.id}".codes != null) { + def i = 0 + def setCode = '' + def child + def usage + def para + state."lock${lock.id}".codes.each { code-> + i++ + child = findAssignedChildApp(lock, i) + setCode = state."lock${lock.id}".codes."slot${i}" + para = "Slot ${i}\nCode: ${setCode}" + if (child) { + para = para + child.getLockUserInfo(lock) + } + paragraph para + + } + } else { + paragraph "No Lock data received yet. Requires custom device driver. Will be populated on next poll event." + doPoll() + } + } + } else { + section() { + paragraph "Error: Can't find lock!" + } + } + } } def installed() { @@ -39,18 +96,36 @@ def updated() { } def initialize() { - // nothing needed here, since the child apps will handle preferences/subscriptions - // this just logs some messages for demo/information purposes def children = getChildApps() + + initalizeLockData() setAccess() subscribe(locks, "reportAllCodes", pollCodeReport, [filterEvents:false]) + log.debug "there are ${children.size()} lock users" +} - log.debug "there are ${children.size()} child smartapps" - childApps.each {child -> - log.debug "child app: ${child.label}" +def initalizeLockData() { + locks.each { lock-> + if (state."lock${lock.id}" == null) { + state."lock${lock.id}" = [:] + } } } +def getLock(params) { + def id = '' + // Assign params to id. Sometimes parameters are double nested. + if (params.id) { + id = params.id + } else if (params.params){ + id = params.params.id + } else if (state.lastLock) { + id = state.lastLock + } + state.lastLock = id + return locks.find{it.id == id} +} + def availableSlots(selectedSlot) { def options = [] (1..30).each { slot-> @@ -86,12 +161,48 @@ def pollCodeReport(evt) { needPoll = true } } - if (needPoll) { + def unmangedCodesNotReady = false + if (overwriteMode) { + unmangedCodesNotReady = removeUnmanagedCodes(evt) + } + if (needPoll || unmangedCodesNotReady) { log.debug 'asking for poll!' runIn(20, doPoll) } } +def removeUnmanagedCodes(evt) { + def codeData = new JsonSlurper().parseText(evt.data) + def lock = locks.find{it.id == evt.deviceId} + def array = [] + def codes = [:] + def codeSlots = 30 + if (codeData.codes) { + codeSlots = codeData.codes + } + + (1..codeSlots).each { slot -> + def child = findAssignedChildApp(lock, slot) + if (!child) { + def currentCode = codeData."code${slot}" + // there is no app associated + if (currentCode != '') { + // Code is set, We should be disabled. + array << ["code${slot}", ''] + } + } + } + def json = new groovy.json.JsonBuilder(array).toString() + if (json != '[]') { + //Lock has codes we don't want + lock.updateCodes(json) + return true + } else { + //Lock is clean + return false + } +} + // def doErrorPoll() { // def needPoll = false // def children = getChildApps() @@ -130,3 +241,26 @@ def setAccess() { def doPoll() { locks.poll() } + +def populateDiscovery(codeData, lock) { + def codes = [:] + def codeSlots = 30 + if (codeData.codes) { + codeSlots = codeData.codes + } + (1..codeSlots).each { slot-> + codes."slot${slot}" = codeData."code${slot}" + } + state."lock${lock.id}".codes = codes +} + +def findAssignedChildApp(lock, slot) { + def children = getChildApps() + def childApp = false + children.each { child -> + if (child.userSlot?.toInteger() == slot) { + childApp = child + } + } + return childApp +} diff --git a/smartapps/ethayer/lock-user.src/lock-user.groovy b/smartapps/ethayer/lock-user.src/lock-user.groovy index 925b9ed..291a50e 100755 --- a/smartapps/ethayer/lock-user.src/lock-user.groovy +++ b/smartapps/ethayer/lock-user.src/lock-user.groovy @@ -3,9 +3,8 @@ definition ( namespace: 'ethayer', author: 'Erik Thayer', description: 'App to manager users. This is a child app.', - category: 'My Apps', + category: 'Safety & Security', - // the parent option allows you to specify the parent app in the form / parent: 'ethayer:Lock Manager', iconUrl: 'https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png', iconX2Url: 'https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png', @@ -440,7 +439,6 @@ def isValidCode() { if (userCode?.isNumber()) { return true } else { - log.debug 'Not a number!' return false } } @@ -686,6 +684,8 @@ def pollCodeReport(evt) { def active = isActive(lock.id) def currentCode = codeData."code${userSlot}" def array = [] + + parent.populateDiscovery(codeData, lock) setKnownCode(currentCode, lock) if (active) { @@ -812,3 +812,14 @@ def sendMessage(msg) { } } } + +def getLockUserInfo(lock) { + def para = "\n${app.label}" + def usage = state."lock${lock.id}".usage + para += " // Usage: ${usage}" + if (!state."lock${lock.id}".enabled) { + def reason = state."lock${lock.id}".disabledReason + para += "\n ${reason}" + } + return para +}