Seeking Better Certainty of Device States

:+1:

If I were to put that line in on() of an existing driver (not onV()) as well as the analogous line into off(), and no additional code, would that force the ensuing event into my Apps regardless of the starting device state?

Yes. You're forcing a changed state on the attribute. But, as I've mentioned before, this could very well have undesirable side effects. I'd only put it in onV and offV. If you have anything triggered by these switches turning on or off, those will trigger when they probably shouldn't. Ah, well, I guess even putting it in onV and offV will do that. Hmmm.. Would be better to use a different attribute instead of "switch".

Usually it's temperature sensor triggering heater, or time of day triggering lights, etc.

Heater events only trigger a "detect" App, then I assign a global variable ValidState to the device state. I need those events passed through to assign ValidState properly, and so I don't think it will mess anything up.

I'm sure it could be done within the driver though, because the driver receives the event from the device without any filtering. I just don't know where/how the device's event is handled (I understand that parse is the first method to process the event). I just don't understand driver operation or Groovy code very well yet.

A properly engineered solution is possible for this. The code I posted was an inadequate POC only, not an engineered solution.

Were it me, and the need for the sort of reliability you seek existed, I sure wouldn't be using Z-Wave or Zigbee for it. There are industrial strength relays, sensors, etc, available, that can be controlled by the hub, or that don't need any hub at all. I've used these in past circumstances. I wouldn't be messing around with drivers checking that commands went through or not. I guess ultimately the best answer depends on how you want to spend your time, money and frustration budget.

2 Likes

SuperVision CC does pretty much the same thing as I'm trying to do. People are using Zwave for their door locks, so that is also a critical situation. Supervision requires the desired event to occur, retries at user-settable intervals up to one minute, up to 5 retries, and if it is ultimately unsuccessful, sends a report to the App. I've only found one person who has tried to incorporate it into driver code though, which he apparently intends to make public, but he currently does not have operational code available. When Supervision gets mature enough and it starts to be incorporated into drivers by convention, things will get easier.

I posted earlier how I'm using dual hubs in a mesh, dual sensors, and wiring my devices to be controllable by more than one device. My hubs each test each other every 5 minutes by toggling a switch paired to the other hub and waiting for the response. With some determination, you can get some serious reliability out of these.

@bravenel how would I get the switch attribute values of "pending on" and "pending off" to be available as choices in the drop-down controls in RM?

You can't, as it knows what values Switch attribute should have. If it were done with a different attribute, it would be possible.

OK, thanks, I can do the same thing using a hub variable.

If you use *changed*, for example in a trigger, then %value% would show "pending on" or "pending off".

@bravenel I've tried inserting this sendEvent line into on() and off() on @jtp10181's driver but calling On from a rule when the switch is already On still results in the event being filtered by the driver. I tried adding an onV() and offV() to do some experimentation but I'm not seeing an onV or offV command in the custom actions drop-down in RM. I've checked in switch and actuator.

That driver must still be considering this not to be a change of state. Not sure why I wasn't seeing the custom commands in RM.

Did you define the commands in the driver metadata? Like this:

Screenshot 2023-06-12 at 8.54.08 PM

Then in RM:

Lastly, I redid the driver I'm using to use a different attribute for this, called "pending". In each of onV and offV, there is sendEvent() to set it to "pending on" or "pending off". Then in the parse() method where it does sendEvent for switch on or switch off, I add sendEvent(name: "pending", value: "set"). Finally, the tests in redoOn, refOn, etc add checking that attribute to the existing test for check and the switch attribute. This catches the case you described where the switch was on, then unplugged, and given an on command.

if(check && (!(device.currentValue("switch") == "on") || device.currentValue("pending") == "pending on")) {

I haven't done this yet, but probably that whole state.checkOn thing could go away and just this pending attribute used instead.

When testing this, I can watch the Current State on the device page. Give it onV command, pending goes to "pending on", and when the device actually turns on, it goes to "set". All of these values should be accessible in RM.

The attribute is defined like this:

    attribute "pending", "enum", ["pending on", "pending off", "set"]

No, I did not. :slightly_smiling_face: That will fix that one.

So I can trigger my "detect" app with the event of "pending" going to "set" is what I suspect you are thinking. Then the state of the switch will evaluate properly in a conditional.

OK, I know you were thinking something different

Not what I'm thinking, no. This code doesn't deal with failure, as in number of retries, how to report failure, etc. It could, and that should be done in the driver, not in rules or apps. The driver would throw an event if max retries is hit, and presumably a simple rule would then notify you of the failure. I might layer that on tomorrow, using driver preferences to set the max retries. That would be another attribute, and a rule would subscribe to that one. Presumably, it would be cleared by the switch being online and obtaining its commanded state.

For now, all I've got is the retry stuff going on, and eventually getting the switch into the commanded state if it comes back online.

@bravenel thank you, I'll see what I can do with this

This approach is much simpler, and the state thing is gone. Here's the code below, much cleaner. Also, no longer any code in the normal on() or off() methods. First method below is called from parse() method (or whatever it calls), right after it sends the switch events. At that point, we know the switch is responding, so no need to go on checking it (I suppose there is a small crack for a device that responds but does the wrong thing -- that would be an easy refinement, but probably not needed).

I pulled that code because there is a bug... pertaining to the aforementioned "refinement". Ha, the refinement is that it should work!

Here it is, working now:

def cancelChecking(onOff) {
    if(onOff == device.currentValue("pending") - "pending ") {
        sendEvent(name: "pending", value: "set")
        switch(onOff) {
            case "on": unschedule(refOn);  unschedule(redoOn); break
            case "off": unschedule(refOff); unschedule(redoOff)
        }
    }
}

def onV() {
    runIn(10, refOn)
    log.debug "onV"
    sendEvent(name: "pending", value: "pending on")
    on()
}

def refOn() {
    log.debug "refOn: device=${device.currentValue("switch")}"
    if(device.currentValue("switch") != "on" || device.currentValue("pending") == "pending on") {
        runIn(10, redoOn)
        refresh()
    }
}

def redoOn() {
    log.debug "redoOn: device=${device.currentValue("switch")}"
    if(device.currentValue("switch") != "on" || device.currentValue("pending") == "pending on") {
        runIn(10, refOn)
        on()
    }
}

def offV() {
    runIn(10, refOff)
    log.debug "offV"
    sendEvent(name: "pending", value: "pending off")
    off()
}

def refOff() {
    log.debug "refOff: device=${device.currentValue("switch")}"
    if(device.currentValue("switch") != "off" || device.currentValue("pending") == "pending off") {
        runIn(10, redoOff)
        refresh()
    }
}

def redoOff() {
    log.debug "redoOff: device=${device.currentValue("switch")}"
    if(device.currentValue("switch") != "off" || device.currentValue("pending") == "pending off") {
        runIn(10, refOff)
        off()
    }
}

In the parse() method, where it does the sendEvent for switch, is where cancelChecking() is called, like this:

        sendEvent(name: "switch", value: switchValue, descriptionText: descriptionText,type:type)
        cancelChecking(switchValue)

Here's the logs from me pulling power while the switch was on, and then giving an offV command. When the switch powers on again, it reports that it is on (which we really don't want to be a state change event). Then the redo code kicks in and turns it off. Notice that it says "Tree is on" when it powers up, but then says "Tree was turned off" when it actually is turned off. That's some subtlety in this driver to get the verbs right.