Skip to content

Commit df4b8d7

Browse files
committed
Discovery and Validation
1 parent 5dd2752 commit df4b8d7

2 files changed

Lines changed: 193 additions & 43 deletions

File tree

readme.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Welcome to Lock Manager!
1111
###Features:
1212
* Assign Codes to Multiple Users.
1313
* Manage Multiple Locks.
14+
* See what codes are active.
1415
* See how many times a code is used (reset usage manually)
1516
* Be notified when a user uses their code.
1617
* Delete codes after they are used*.

user-lock-manager.smartapp.groovy

Lines changed: 192 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* User Lock Manager v3.8.4.1
2+
* User Lock Manager v3.9
33
*
44
* Copyright 2015 Erik Thayer
55
*
@@ -28,7 +28,9 @@ definition(
2828
page(name: "resetAllCodeUsagePage")
2929
page(name: "resetCodeUsagePage")
3030
page(name: "reEnableUserPage")
31-
page(name: "discoveryPage")
31+
page(name: "infoPage")
32+
page(name: "infoRefreshPage")
33+
page(name: "lockInfoPage")
3234
}
3335

3436
def rootPage() {
@@ -43,6 +45,7 @@ def rootPage() {
4345
section {
4446
input name: "maxUsers", title: "Number of users", type: "number", multiple: false, refreshAfterSelection: true, submitOnChange: true
4547
href(name: "toSetupPage", title: "User Settings", page: "setupPage", description: setupPageDescription(), state: setupPageDescription() ? "complete" : "")
48+
href(name: "toInfoPage", page: "infoPage", title: "Lock Info")
4649
href(name: "toNotificationPage", page: "notificationPage", title: "Notification Settings", description: notificationPageDescription(), state: notificationPageDescription() ? "complete" : "")
4750
href(name: "toSchedulingPage", page: "schedulingPage", title: "Schedule (optional)", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "")
4851
href(name: "toOnUnlockPage", page: "onUnlockPage", title: "Global Hello Home")
@@ -83,10 +86,22 @@ def userPage(params) {
8386

8487
if (!state."userState${i}".enabled) {
8588
section {
86-
paragraph "This user has been disabled by the controller due to excessive failed set attempts! Please verify that the code is valid and does not conflict with another code.\n\nYou may attempt to delete the code field and re-enter it.\n\nTo re-enabled this slot, click 'Reset' link bellow."
89+
paragraph "WARNING:\n\nThis user has been disabled.\nReason: ${state."userState${i}".disabledReason}"
8790
href(name: "toreEnableUserPage", title: "Reset User", page: "reEnableUserPage", params: [number: i], description: "Tap to reset")
8891
}
8992
}
93+
if (settings."userCode${i}") {
94+
def conflict = getConflicts(i)
95+
if (conflict.has_conflict) {
96+
section("Conflicts:") {
97+
locks.each { lock->
98+
if (conflict."lock${lock.id}".conflicts != null) {
99+
paragraph "${lock.displayName} slot ${fancyString(conflict."lock${lock.id}".conflicts)}"
100+
}
101+
}
102+
}
103+
}
104+
}
90105
section("Code #${i}") {
91106
input(name: "userName${i}", type: "text", title: "Name for User", defaultValue: settings."userName${i}")
92107
def title = "Code (4 to 8 digits)"
@@ -95,7 +110,7 @@ def userPage(params) {
95110
title = "Code (Must be ${lock.pinLength} digits)"
96111
}
97112
}
98-
input(name: "userCode${i}", type: "text", title: title, required: false, defaultValue: settings."userCode${i}")
113+
input(name: "userCode${i}", type: "text", title: title, required: false, defaultValue: settings."userCode${i}", refreshAfterSelection: true)
99114
input(name: "userSlot${i}", type: "number", title: "Slot (1 through 30)", defaultValue: preSlectedCode(i))
100115
}
101116
section {
@@ -281,29 +296,65 @@ def getUser(params) {
281296
return i
282297
}
283298

284-
def discoveryPage() {
285-
dynamicPage(name:"discoveryPage", title:"User Settings") {
286-
if (locks {
287-
section('locks') {
299+
def getLock(params) {
300+
def id = ''
301+
// Assign params to id. Sometimes parameters are double nested.
302+
if (params.id) {
303+
id = params.id
304+
} else if (params.params){
305+
id = params.params.id
306+
} else if (state.lastLock) {
307+
id = state.lastLock
308+
}
309+
310+
state.lastLock = id
311+
return locks.find{it.id == id}
312+
}
313+
314+
def infoPage(params) {
315+
dynamicPage(name:"infoPage", title:"Lock Info") {
316+
section() {
317+
href(name: "toInfoRefreshPage", page: "infoRefreshPage", title: "Refresh Lock Data", description: 'Tap to refresh')
318+
}
319+
section("Locks") {
320+
if (locks) {
288321
locks.each { lock->
289-
if (!state."userState${user}") {
290-
//there's no values, so reset
291-
resetCodeUsage(user)
292-
}
293-
href(name: "toLockPage${i}", page: "userPage", params: [number: i], required: false, description: userHrefDescription(user), title: userHrefTitle(user), state: userPageState(user) )
322+
href(name: "toLockInfoPage${i}", page: "lockInfoPage", params: [id: lock.id], required: false, title: lock.displayName )
294323
}
295324
}
296-
section {
297-
href(name: "toResetAllCodeUsage", title: "Reset Code Usage", page: "resetAllCodeUsagePage", description: "Tap to reset")
298-
}
299-
} else {
300-
section("Users") {
301-
paragraph "Users are set to zero. Please go back to the main page and change the number of users to at least 1."
302-
}
325+
}
326+
}
327+
}
328+
def infoRefreshPage() {
329+
dynamicPage(name:"infoRefreshPage", title:"Lock Info") {
330+
section() {
331+
manualPoll()
332+
paragraph "Lock info refreshing soon."
333+
href(name: "toInfoPage", page: "infoPage", title: "Back to Lock Info")
303334
}
304335
}
305336
}
306337

338+
def lockInfoPage(params) {
339+
dynamicPage(name:"lockInfoPage", title:"Lock Info") {
340+
def lock = getLock(params)
341+
section() {
342+
if (lock) {
343+
if (state."lock${lock.id}".codes != null) {
344+
def i = 0
345+
def pass = ''
346+
state."lock${lock.id}".codes.each { code->
347+
i++
348+
pass = state."lock${lock.id}".codes."slot${i}"
349+
paragraph "Slot ${i}\nCode: ${pass}"
350+
}
351+
} else {
352+
paragraph "No Lock data received yet. Requires custom device driver. Will be populated on next poll event."
353+
}
354+
}
355+
}
356+
}
357+
}
307358

308359
public smartThingsDateFormat() { "yyyy-MM-dd'T'HH:mm:ss.SSSZ" }
309360

@@ -314,6 +365,96 @@ public humanReadableEndDate() {
314365
new Date().parse(smartThingsDateFormat(), endTime).format("h:mm a", timeZone(endTime))
315366
}
316367

368+
def manualPoll() {
369+
locks.poll()
370+
}
371+
372+
def getConflicts(i) {
373+
def currentCode = settings."userCode${i}"
374+
def currentSlot = settings."userSlot${i}"
375+
def conflict = [:]
376+
conflict.has_conflict = false
377+
378+
379+
locks.each { lock->
380+
if (state."lock${lock.id}".codes) {
381+
conflict."lock${lock.id}" = [:]
382+
conflict."lock${lock.id}".conflicts = []
383+
def ind = 0
384+
state."lock${lock.id}".codes.each { code ->
385+
ind++
386+
if (currentSlot.toInteger() != ind.toInteger() && !isUnique(currentCode, state."lock${lock.id}".codes."slot${ind}")) {
387+
conflict.has_conflict = true
388+
state."userState${i}".enabled = false
389+
state."userState${i}".disabledReason = "Code Conflict Detected"
390+
conflict."lock${lock.id}".conflicts << ind
391+
}
392+
}
393+
}
394+
}
395+
396+
return conflict
397+
}
398+
399+
def isUnique(newInt, oldInt) {
400+
def newArray = []
401+
def oldArray = []
402+
403+
404+
// just to get the size
405+
oldInt.toList().collect { oldArray << normalizeNumber(it.toInteger()) }
406+
def oldSize = oldArray.size()
407+
oldArray = []
408+
409+
def i = 0
410+
newInt.toList().collect {
411+
i++
412+
if (i <= oldSize) {
413+
newArray << normalizeNumber(it.toInteger())
414+
}
415+
}
416+
417+
i = 0
418+
oldInt.toList().collect {
419+
i++
420+
if (i <= newArray.size()) {
421+
oldArray << normalizeNumber(it.toInteger())
422+
}
423+
}
424+
425+
def result = true
426+
i = 0
427+
newArray.each { num->
428+
i++
429+
if (newArray.join() == oldArray.join()) {
430+
result = false
431+
}
432+
}
433+
return result
434+
}
435+
436+
def normalizeNumber(number) {
437+
def result = null
438+
switch (number) {
439+
case [1,2]:
440+
result = 1
441+
break
442+
case [3,4]:
443+
result = 2
444+
break
445+
case [5,6]:
446+
result = 3
447+
break
448+
case [7,8]:
449+
result = 4
450+
break
451+
case [9,0]:
452+
result = 5
453+
break
454+
}
455+
return result
456+
}
457+
317458
def setupPageDescription(){
318459
def parts = []
319460
for (int i = 1; i <= settings.maxUsers; i++) {
@@ -531,6 +672,8 @@ private initialize() {
531672
revokeDisabledUsers()
532673
reconcileCodes()
533674
lockErrorLoopReset()
675+
initalizeLockData()
676+
534677
log.debug "state: ${state}"
535678
}
536679

@@ -551,16 +694,25 @@ def resetCodeUsage(i) {
551694
def enableUser(i) {
552695
state."userState${i}".enabled = true
553696
}
697+
698+
def initalizeLockData() {
699+
locks.each { lock->
700+
if (state."lock${lock.id}" == null) {
701+
state."lock${lock.id}" = [:]
702+
}
703+
}
704+
}
554705
def lockErrorLoopReset() {
555706
state.error_loop_count = 0
556-
def i = 0
557707
locks.each { lock->
558-
i = i + 1
559-
state."lock${i}" = [:]
560-
state."lock${i}".error_loop = false
708+
if (state."lock${lock.id}" == null) {
709+
state."lock${lock.id}" = [:]
710+
}
711+
state."lock${lock.id}".error_loop = false
561712
}
562713
}
563714

715+
564716
def locationHandler(evt) {
565717
log.debug "locationHandler evt: ${evt.value}"
566718
if (modeStart) {
@@ -825,11 +977,17 @@ def codereturn(evt) {
825977
if (codeNumber == "") {
826978
if (notifyAccessEnd) {
827979
def message = "${userName} no longer has access to ${evt.displayName}"
980+
if (codeNumber.isNumber()) {
981+
state."lock${evt.deviceId}".codes."slot${codeSlot}" = codeNumber
982+
}
828983
send(message)
829984
}
830985
} else {
831986
if (notifyAccessStart) {
832987
def message = "${userName} now has access to ${evt.displayName}"
988+
if (codeNumber.isNumber()) {
989+
state."lock${evt.deviceId}".codes."slot${codeSlot}" = codeNumber
990+
}
833991
send(message)
834992
}
835993
}
@@ -1023,25 +1181,16 @@ def pollCodeReport(evt) {
10231181
}
10241182
}
10251183
}
1026-
def i = 0
1027-
def currentLockNumber = 0
1028-
def currentLock = [:]
1029-
locks.each { lock->
1030-
i = i + 1
1031-
if (lock.id == evt.deviceId) {
1032-
currentLock = lock
1033-
currentLockNumber = i
1034-
}
1035-
}
10361184

1037-
populateDiscovery(codeData, currentLockNumber)
1185+
def currentLock = locks.find{it.id == evt.deviceId}
1186+
populateDiscovery(codeData, currentLock)
10381187

10391188
def json = new groovy.json.JsonBuilder(array).toString()
10401189
if (json != '[]') {
10411190
runIn(60*2, doPoll)
10421191

10431192
//Lock is in an error state
1044-
state."lock${currentLockNumber}".error_loop = true
1193+
state."lock${currentLock.id}".error_loop = true
10451194
def error_number = state.error_loop_count + 1
10461195
if (error_number <= 10) {
10471196
log.debug "sendCodes fix is: ${json} Error: ${error_number}/10"
@@ -1057,12 +1206,13 @@ def pollCodeReport(evt) {
10571206
def name = settings."userName${usedIndex}"
10581207
if (state."userState${usedIndex}".enabled) {
10591208
state."userState${usedIndex}".enabled = false
1209+
state."userState${usedIndex}".disabledReason = "Controller failed to set code"
10601210
send("Controller failed to set code for ${name}")
10611211
}
10621212
}
10631213
}
10641214
} else {
1065-
state."lock${currentLockNumber}".error_loop = false
1215+
state."lock${currentLock.id}".error_loop = false
10661216
if (allCodesDone) {
10671217
lockErrorLoopReset()
10681218
} else {
@@ -1115,11 +1265,10 @@ private sendMessage(msg) {
11151265
}
11161266
}
11171267

1118-
def populateDiscovery(codeData, lockNumber) {
1119-
def codes = []
1120-
(1..codeData.codes).each { user->
1121-
codes << [user, codeData."code${user}"]
1268+
def populateDiscovery(codeData, lock) {
1269+
def codes = [:]
1270+
(1..codeData.codes).each { slot->
1271+
codes."slot${slot}" = codeData."code${slot}"
11221272
}
1123-
state."lock${lockNumber}".codes = codes
1124-
log.debug state."lock${lockNumber}".codes
1273+
state."lock${lock.id}".codes = codes
11251274
}

0 commit comments

Comments
 (0)