Seeking Better Certainty of Device States

You shouldn't even need to test (query) for the desired state. Just keep asking for it to be put into that state until it confirms that it is. Sending a GET + receiving a REPORT is the same amount of airtime/packets as just asking it to be placed into that state (SET + SUPERVISED REPORT). The first retries would be handled by the chipset (tries every 500ms X times, etc). The driver could/would have a retry layer on top of that if needed.

I also don't think the ZEN17 would be any more difficult to work with either. Its still just a binary switch, even though it has multiple endpoints, etc. I would suspect the biggest issue is the level of support that each of the devices you're thinking of using has for S2 Supervision, etc. Zooz tends to be responsive to any issues/bugs pointed out though.

Just FYI, I have a ZEN16 and ZEN17 available for development use. I don't have the other units you mentioned.

1 Like

Do you mean the chipset of the device? The device retries on its own? Is the retry time interval or number of retries able to be set by the driver?

The zwave chip itself in both the hub and the device. When sending supervised encapsulated data, they can each do their own retries for failed acks, checksum failures, decryption failures etc.

Devices usually offer configurability or some reasonable defaults if not. For instance, the ZCOMBO smoke/CO alarm offers:


Then on TOP of that if the driver needs to do additional retry/error handling it can do so using whatever logic needed.

@coreystup That's all very interesting. My belief is that the primary failures I see are either the device not receiving the command or the hub not receiving the ACK. Maybe there are some checksum failures in there as well, but I was seeing the same occasional failures before I was using S2.

There is a Supervision command class on the Zen17 btw, which I imagine you would expect being that is an S2 device.

@bertabcd1234 I can only find a few references of users attempting to include Supervision into custom driver code, but I can't find any examples of anyone actually testing it and using it. Did you find some posts that I am missing? It's a great capability in Zwave, but it seems like the only places it is being used are commercial security devices and the like, such as Zwave doorlocks etc., not custom driver code.

Not that there is any reason it wouldn't work in custom code. Just can't find any examples.

Yes, also linked to in the docs:

The driver you were previously referred to may also be using it already (or not; I did not look at the code).

This is correct, and it is a case that needs to be dealt with by some means.

I am going to create a modified driver this morning that does this. My approach will not be Z-Wave specific, won't be using Supervision CC, so it won't be limited to S2 devices. Instead, a simple check and repeat approach. It certainly could have a settable maximum number of retries after which it throws some event (sets an attribute), and also a settable refresh interval. This is not a difficult thing to do, and I will share the core part of what I come up with.

4 Likes

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.

6 Likes

Or what about add the Initialize capability to the driver, and if a preference is enabled ("Recover from hub offline") then it restarts the previous checking.

3 Likes

Yeah. That’s good. I forgot about Initialize capability. No rule needed.

@bravenel this is awesome. It is going to take me some time to digest and absorb this properly because I have minimal familiarity with Groovy and driver structure, but I'm generally understanding the code. Most of my lack of comprehension comes from which words are Hubitat keywords/reserved words, which are general conventions for variables/methods in Hubitat drivers, and which must be defined in the driver. I suspect that you have defined everthing here that you need to though.

Particularly "onCheck", I haven't seen that, is that a predefined State in Hubitat drivers?

Somehow I was thinking Initialize was a method that was called when the device was powered up, and I saw your suggestion about hub offline/reboot as addressing when the hub rebooted. Does Initialize get called in both situations? Are you suggesting using an attribute instead of a state because attributes are preserved through a hub reboot and states are not?

If the added commands onV() and offV() were left in the driver, then cancelOffCheck() and cancelOnCheck() would need to remain in those methods as well as be added to on() and off() I presume.

Thanks again.

No, state objects are just inventions of the coder.

The initialize method is called when the device is created. The initialize capability causes that method to be called upon hub startup, which is a different kettle of fish. This is used for things such as reconnecting to telnet for a driver that uses telnet, etc. So it could serve the purpose of restarting the checking after a reboot if it was happening before.

No, I did that to expose it to RM, but if initialize is used, it doesn't need to be an attribute, and state would work fine. Both state and attributes are preserved across reboots.

No, because in each case the corresponding method is called. onV() calls on(). No need to cancel checking twice. But there is a need to cancel it in on() and off() in case those are commanded instead of onV() or offV().

runIn() is a hub service method that schedules something to happen in the future. device.currentValue() is a built-in method that returns the current value of a device attribute. on(), onV(), refresh(), etc. are all names of methods in the driver. Certain methods are mandatory depending on the Capabilities defined. For example, Switch Capability mandates that the driver must implement on() and off(). onV() and offV() were declared to be commands of the driver in its metadata section:

        command "onV"
        command "offV"

That causes those methods to show up on the device page. exposing them to the world.

Yes, I had seen those metadata command declarations in a custom driver for an Enerwave dual relay that I've been using as a learning tool. I haven't seen them in the Hubitat tutorials on custom drivers though. That clears things up for me.

I would like to try to include your code in driver code for devices I use and try to get it to work. Do you know, are you able to make public the built-in drivers for the Zooz Zen17, Zen04, or Aeotec SmartSwitch7?

Start with @jtp10181's Zooz driver. Get one to work.

The best way to learn Groovy, imo, is to modify existing code, and get the modifications to work as expected. You can freely sprinkle in log.debug to see things as you go, and then take them out later. This particular code is pretty simple and straightforward, so is a good place to start.

1 Like

I don't have s full driver for the ZEN17 but I do for the ZEN04: [DRIVER] Zooz Smart Plugs Advanced (ZEN04 / ZEN05 / ZEN14 / ZEN15)

2 Likes

OK, I can do that with the Zen04.

One more thing, if onCheck is the invention of the coder, was that pre-existing in the driver you were modifying?

Yes, thanks, I had installed and was experimenting with your Zen17 driver last night to see if Supervision parameters were available, and I was looking at the code for the Zen04 just to try to understand the general makeup of Hubitat drivers and Groovy.

No, I just made it up for this code. A state variable that has not been assigned a value starts out as null. That's assumed in this code.

BTW, the code to add to initialize() (once Capability "Initialize" is added) is this:

    if(state.onCheck) redoOn() 
    else if(state.offCheck) redoOff()

@bravenel does onCheck need to be defined anywhere?