-
Notifications
You must be signed in to change notification settings - Fork 1
/
jvm.GenericComponentSchlageBE469ZPLock.groovy
215 lines (181 loc) · 7.06 KB
/
jvm.GenericComponentSchlageBE469ZPLock.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import java.util.concurrent.*
import groovy.transform.Field
metadata {
definition (name: "Generic Component Schlage BE469ZP Lock", namespace: "jvm", author: "jvm") {
capability "Initialize"
capability "Actuator"
capability "Lock"
capability "Lock Codes"
capability "Refresh"
capability "ContactSensor"
attribute "lastCodeName", "STRING"
}
preferences{
input name: "optEncrypt", type: "bool", title: "Enable lockCode encryption", defaultValue: false, description: ""
//standard logging options for all drivers
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false, description: ""
input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true, description: ""
}
}
void logsOff(){
log.warn "debug logging disabled..."
device.updateSetting("logEnable",[value:"false",type:"bool"])
}
void installed(){
log.warn "installed..."
sendEvent(name:"maxCodes",value:30)
// sendEvent(name:"codeLength",value:4)
}
void initialize() {
sendEvent(name:"maxCodes",value:30)
}
void updated() {
log.info "updated..."
//check crnt lockCodes for encryption status
updateEncryption()
//turn off debug logs after 30 minutes
if (logEnable) runIn(1800,logsOff)
}
void parse(List description) {
description.each {
if (it.name == "parameterUpdate") {
if (it.cmd?.parameterNumber == 16) {
Integer length = it.cmd.scaledConfigurationValue
sendEvent([name:"codeLength",value:length,descriptionText:"Device ${device.displayName}: codeLength set to ${length}"])
} else if (it.cmd?.parameterNumber == 16) {
log.debug "Set code length to ..."
}
} else if (device.hasAttribute (it.name)) {
if (txtEnable) log.info it.descriptionText
sendEvent(it)
}
}
}
//capability commands
void refresh() {
sendEvent(name:"lock", value: device.currentValue("lock"))
}
void lock(){
parent?.componentLock(this.device)
}
void unlock(){
parent?.componentUnLock(this.device)
}
void setCodeLength(length){
if ( (4..8).contains((int) length)) {
parent?.setParameter(parameterNumber: 0x10, value:((int) length))
} else {
log.error "Device ${device.displayName}: Incorrect code length. Valid values are 4 to 8"
}
}
////////// Stuff above this line is working! /////////////
void setCode(codeNumber, code, name = null) {
/*
on sucess
name value data notes
codeChanged added | changed [<codeNumber>":["code":"<pinCode>", "name":"<display name for code>"]] default name to code #<codeNumber>
lockCodes JSON map of all lockCode
*/
if (codeNumber == null || codeNumber == 0 || code == null) return
if (logEnable) log.debug "setCode- ${codeNumber}"
if (!name) name = "code #${codeNumber}"
Map lockCodes = getLockCodes()
Map codeMap = getCodeMap(lockCodes,codeNumber)
if (!changeIsValid(lockCodes,codeMap,codeNumber,code,name)) return
Map data = [:]
String value
if (logEnable) log.debug "setting code ${codeNumber} to ${code} for lock code name ${name}"
if (codeMap) {
if (codeMap.name != name || codeMap.code != code) {
codeMap = ["name":"${name}", "code":"${code}"]
lockCodes."${codeNumber}" = codeMap
data = ["${codeNumber}":codeMap]
if (optEncrypt) data = encrypt(JsonOutput.toJson(data))
value = "changed"
}
} else {
codeMap = ["name":"${name}", "code":"${code}"]
data = ["${codeNumber}":codeMap]
lockCodes << data
if (optEncrypt) data = encrypt(JsonOutput.toJson(data))
value = "added"
}
updateLockCodes(lockCodes)
sendEvent(name:"codeChanged",value:value,data:data, isStateChange: true)
}
void deleteCode(codeNumber) {
parent?.componentDeleteCode(codeNumber)
}
//virtual test methods
void testSetMaxCodes(length){
//on a real lock this event is generated from the response to a configuration report request
sendEvent(name:"maxCodes",value:length)
}
void testUnlockWithCode(code = null){
if (logEnable) log.debug "testUnlockWithCode: ${code}"
/*
lockCodes in this context calls the helper function getLockCodes()
*/
Object lockCode = lockCodes.find{ it.value.code == "${code}" }
if (lockCode){
Map data = ["${lockCode.key}":lockCode.value]
String descriptionText = "${device.displayName} was unlocked by ${lockCode.value.name}"
if (txtEnable) log.info "${descriptionText}"
if (optEncrypt) data = encrypt(JsonOutput.toJson(data))
sendEvent(name:"lock",value:"unlocked",descriptionText: descriptionText, type:"physical",data:data, isStateChange: true)
sendEvent(name:"lastCodeName", value: lockCode.value.name, descriptionText: descriptionText, isStateChange: true)
} else {
if (txtEnable) log.debug "testUnlockWithCode failed with invalid code"
}
}
//helpers
Boolean changeIsValid(lockCodes,codeMap,codeNumber,code,name){
//validate proposed lockCode change
Boolean result = true
Integer maxCodeLength = device.currentValue("codeLength")?.toInteger() ?: 4
Integer maxCodes = device.currentValue("maxCodes")?.toInteger() ?: 20
Boolean isBadLength = code.size() > maxCodeLength
Boolean isBadCodeNum = maxCodes < codeNumber
if (lockCodes) {
List nameSet = lockCodes.collect{ it.value.name }
List codeSet = lockCodes.collect{ it.value.code }
if (codeMap) {
nameSet = nameSet.findAll{ it != codeMap.name }
codeSet = codeSet.findAll{ it != codeMap.code }
}
Boolean nameInUse = name in nameSet
Boolean codeInUse = code in codeSet
if (nameInUse || codeInUse) {
if (nameInUse) { log.warn "changeIsValid:false, name:${name} is in use:${ lockCodes.find{ it.value.name == "${name}" } }" }
if (codeInUse) { log.warn "changeIsValid:false, code:${code} is in use:${ lockCodes.find{ it.value.code == "${code}" } }" }
result = false
}
}
if (isBadLength || isBadCodeNum) {
if (isBadLength) { log.warn "changeIsValid:false, length of code ${code} does not match codeLength of ${maxCodeLength}" }
if (isBadCodeNum) { log.warn "changeIsValid:false, codeNumber ${codeNumber} is larger than maxCodes of ${maxCodes}" }
result = false
}
return result
}
void getCodes() {
parent?.componentGetCodes(this.device, 5)
}
void mySettings() {
parent?.testGetSettings(this)
}
void updateEncryption(){
/*
resend lockCodes map when the encryption option is changed
*/
String lockCodes = device.currentValue("lockCodes") //encrypted or decrypted
if (lockCodes){
if (optEncrypt && lockCodes[0] == "{") { //resend encrypted
sendEvent(name:"lockCodes",value: encrypt(lockCodes))
} else if (!optEncrypt && lockCodes[0] != "{") { //resend decrypted
sendEvent(name:"lockCodes",value: decrypt(lockCodes))
}
}
}