Here is the code for POC of this. I added two commands to the driver, onV and offV. These could be commanded with RM custom commands. For now, the refresh rate is hardwired, and there is no max retries check. I tested this by turning off the switch, then unplugging it; turned it on from the device page, and after some time plugged it back in. Works as expected. Here are the logs:
Notes on the logs: The refOff at 44:11.285 is after offV() checking that the light is indeed off. The next refOn at 45:40.740 is 10 seconds after I gave the onV() command on the device page, checking to see if it turned on. It did not since it was unplugged. At 46:10.843 the redoOn attempts again to turn it on, and this time succeeds. The final refOn at 46:20.862 is when the sequence stops, as the switch has responded to the command and is now on.
The code for this would work in most drivers:
def onV() {
cancelOffCheck()
state.onCheck = true
runIn(10, refOn)
on()
}
def refOn() {
Boolean check = state.onCheck
state.onCheck = false
log.debug "refOn: check=$check, device=${device.currentValue("switch")}"
if(check && !(device.currentValue("switch") == "on")) {
state.onCheck = true
runIn(10, redoOn)
refresh()
}
}
def redoOn() {
Boolean check = state.onCheck
state.onCheck = false
log.debug "redoOn: check=$check, device=${device.currentValue("switch")}"
if(check && !(device.currentValue("switch") == "on")) {
state.onCheck = true
runIn(10, refOn)
on()
}
}
def cancelOffCheck() {
state.offCheck = false
unschedule(refOff)
unschedule(redoOff)
}
def offV() {
cancelOnCheck()
state.offCheck = true
runIn(10, refOff)
off()
}
def refOff() {
Boolean check = state.offCheck
state.offCheck = false
log.debug "refOff: check=$check, device=${device.currentValue("switch")}"
if(check && !(device.currentValue("switch") == "off")) {
state.offCheck = true
runIn(10, redoOff)
refresh()
}
}
def redoOff() {
Boolean check = state.offCheck
state.offCheck = false
log.debug "redoOff: check=$check, device=${device.currentValue("switch")}"
if(check && !(device.currentValue("switch") == "off")) {
state.offCheck = true
runIn(10, refOff)
off()
}
}
def cancelOnCheck() {
state.onCheck = false
unschedule(refOn)
unschedule(redoOn)
}
The calls to cancelOnCheck()
and cancelOffCheck()
should be added to the off()
and on()
methods (with test that it's checking) instead of where they are in this code. My test driver is actually a dimmer driver, so cancelOffCheck()
would also be added to setLevel()
as well. So if the device is turned on with onV()
, and then turned off with off()
, it doesn't continue to check.
To handle the hub offline and reboot issue, I think the state variables state.onCheck
and state.offCheck
would instead be an attribute, with values like [checkingOn, checkingOff, notChecking]
. Then for those devices where recovering from hub offline is important, an RM rule with trigger of systemStart
would check that attribute value, and if checkingOn
would do onV
to the device, etc. This restarts the checking for the device that hadn't responded before the reboot. Like this (my test device is called Tree):
Finally, nothing says this has to be done with a new (custom) command in the driver. This could be done directly in the on() and off() methods, effectively changing the driver from fire and forget, to fire and keep trying until success. And choosing this option could be made a preference in the driver, so a single driver can serve both approaches.