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
3436def 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\n You may attempt to delete the code field and re-enter it. \n\n To re-enabled this slot, click 'Reset' link bellow. "
89+ paragraph " WARNING: \n\n This user has been disabled. \n Reason: ${ 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} \n Code: ${ 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
308359public 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+
317458def 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) {
551694def 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+ }
554705def 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+
564716def 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