I ended up going this way. It’s working fine, but man is that fugly.
For the lock - when in locked state (which is the only state the lock itself fires the tampered notification) I wanted the main tile to be green if all is well, and yellowish if someone had tried to guess the code and tripped the tamper. But I didn’t want to introduce new states for device.lock - that would potentially break/confuse other smart apps. So as Terry suggested I just made a second custom attribute with one additional state (lockedtampered) so my main tile could be yellowish if it was locked and tampered.
The main tile color resets to normal if the lock is successfully changed in state (to any normal state - unlocked, etc) by any means. But the tamper smaller tile remains tripped until you press the button in the device page to clear the tamper attribute.
I think that’s reasonable behavior. I think that’s gawd awful to implement.
Normal state:
Someone is being naughty
But then we successfully unlock the door
And finally we clear the tamper by tapping it
What might be more reasonable IMHO is to allow access to other attributes in the tiles/state section like I was trying to do…
You end up with something like this to make the shadow state work - code shown just to demonstrate technique, I’m not sure if these device types are terribly useful for others.
Add some metadata
command "resetTamperAlert" // reset tamper state
attribute "tamper", "String" //, ["clear", "detected"]
attribute "locktamper", "String" //, ["locked", "lockedtamper"...]
Change the main tile to use your custom attribute
standardTile("toggle", "device.locktamper", width: 2, height: 2) {
state "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
state "lockedtamper", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#e3eb00", nextState:"unlocking"
state "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
state "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
state "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
state "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
}
Add a tile to show tamper notification and tap it to clear
standardTile("tamper", "device.tamper") {
state("clear", label:'secure', icon:"st.security.alarm.clear", backgroundColor:"#79b821")
state("detected", label:'tampered', action:"resetTamperAlert", icon:"st.security.alarm.alarm", backgroundColor:"#e3eb00")
}
Mirror state changes from the real attribute to the shadow attribute to maintain parallel state and make device behave as expected. In the zwave lock this happens in this function, so I add a second event which sets the shadow attribute here. If I didn’t want to “clear” the display for tamper in all states, I would need *tamper values for unlocked/etc. I also didn’t bother with the intermediate “unlocking” states. Pretty much the big tile will only show tamper/locked until anything changes it, but that’s good enough for me for at-a-glance seeing something is amiss.
def zwaveEvent(DoorLockOperationReport cmd) {
def result = []
def map = [ name: "lock" ]
def maptamper = [ name: "locktamper" ]
if (cmd.doorLockMode == 0xFF) {
maptamper.value = map.value = "locked"
} else if (cmd.doorLockMode >= 0x40) {
maptamper.value = map.value = "unknown"
} else if (cmd.doorLockMode & 1) {
maptamper.value = map.value = "unlocked with timeout"
} else {
maptamper.value = map.value = "unlocked"
if (state.assoc != zwaveHubNodeId) {
log.debug "setting association"
result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)))
result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId))
result << response(secure(zwave.associationV1.associationGet(groupingIdentifier:1)))
}
}
sendEvent(maptamper) // set the shadow attribute
result ? [createEvent(map), *result] : createEvent(map)
}
Generate “extended” state to the custom attribute when the oddball state happens (tamper tripped) - this is buried inside the alarm zwave function - case 161 is the tamper message from the lock
case 161:
if (cmd.alarmLevel == 2) {
map = [ descriptionText: "$device.displayName front escutcheon removed", isStateChange: true ]
} else {
// lock was tampered - set tampered state
sendEvent(displayed: false, name: "locktamper", value: "lockedtamper")
// trip alarm
map = [ name: "tamper", value: "detected", isStateChange: true, descriptionText: "$device.displayName has had a failed code entered" ]
}
break
And give them a manual reset for the tamper tile
// for tamper
def resetTamperAlert() {
// get lock display out of tamper state by setting it to whatever the lock state actually is
// this should happen on successful unlock or lock as well
sendEvent(displayed: true, isStateChange: true, name: "locktamper", value: device.currentValue("lock"))
sendEvent(displayed: true, isStateChange: true, name: "tamper", value: "clear", descriptionText: "$device.displayName tamper state cleared")
}
(edit - bonus points for posting screenshots when my CoC troops were ready )