Help - How to use Supervision Get

I am trying to figure out how to properly format a command for sending using the SupervisionGet command. I was assuming that encapsulation using supervisionGet would be similar to how you do it for the multiChanngelV2.multiChannelCmdEncap() function where you use an encapsulate(cmd) method, so I similarly set up a very simple test function that should print out the encapsulated command where the command to be encapsulated is a basicGet. The function I used was:

void test()
{
    def cmd = secure(zwave.basicV1.basicGet())
    log.debug "Command string: " + cmd
    
    log.debug "Command String after supervision encapsulation: " + zwave.supervisionV1.supervisionGet(sessionID: 1 , statusUpdates: false).encapsulate(cmd)
}

In response, I got the following error:

dev:11432021-01-09 00:08:09.947 errorgroovy.lang.MissingMethodException: No signature of method: hubitat.zwave.commands.supervisionv1.SupervisionGet.encapsulate() is applicable for argument types: (java.lang.String) values: [2002] on line 764 (test)
dev:11432021-01-09 00:08:09.912 debugCommand string: 2002

As shown, the command produced was a 2002 (which is the expected command for BasicGet, but when the supervisionGet(...).encapsulate(cmd) was called, no method was found.

My question - what did I do wrong when trying to call the supervisionGet? Is supervisionGet() missing an encapsulate(cmd) method so I can't call supervisionGet(...).encapsulate(cmd)?

This is whats in the drivers I am working on, which I pretty much stole from Hubitat example drivers. There is more going on than just this one function so I would check out the example driver below.

void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) {
	logTrace "${cmd}"
	def encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
	
	if (encapsulatedCmd) {
		zwaveEvent(encapsulatedCmd)
	} else {
		log.warn "Unable to extract encapsulated cmd from $cmd"
	}

	sendHubCommand(new hubitat.device.HubAction(secureCmd(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)), hubitat.device.Protocol.ZWAVE))
}

Example Driver:

I see you are trying to SEND a command, what I posted above I believe is receiving messages back. For sending everything goes through the secureCmd function in the example I posted. It will automatically secure the message if the device is paired with security. I think this was added in Hubitat Firmware 2.2.3 or 2.2.2

String switchBinaryGetCmd() {
	return secureCmd(zwave.switchBinaryV1.switchBinaryGet())
}

String secureCmd(String cmd){
	return zwaveSecureEncap(cmd)
}

String secureCmd(hubitat.zwave.Command cmd){
	return zwaveSecureEncap(cmd)
}

Thanks for the help you've offered, but its not what I'm after. Supervision is different from security and can be used with secure or non-secure nodes. I can't find any driver that actually uses Supervision to send commands to nodes (there are examples for receiving, as you've pointed out), but its a pretty useful function as it ensures that a command gets executed or, if there is a problem, you get a report explaining what happened. I'm working on adding supervision to a "universal" driver that I've written (here: GitHub - jvmahon/HubitatCustom and on package manager) so as to increase the reliability of z-wave networks. Its actually not that hard to format the message myself, but it seems that Hubitat would likely have (or should have) a ().ensapsulate(cmd) method already to be consistent with some of their implemetation of other z-wave classes that perform encapsulation.

Well seems you know much more than I do already. Looking at it closer if it is supposed to work like multiChannelCmdEncap().encapsulate(cmd) then you do have it written correctly, but it is not liking the String class being passed or there is no method at all named encapsulate.

Here is a multichannel encapsulated command in the driver I am working on. Most of this is ported from ST so going to be honest I don't know 100% what all of it does yet but it works and I am starting to make sense of all of it.

secureCmd(zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:safeToInt(endpoint)).encapsulate(cmd))

I'm guessing that it's this and it would be a simple addition.

So I was reading up on this more today and I think what I am understanding is that if the device has supervision implemented properly we could send all the commands from the driver supervision encapsulated, and the devoice should immediately reply back using a supervisionReport with a status. Not sure if this is useful for a simple light switch or what I would do with the info in the supervision report, but still good stuff to know.

Did I see you hint someplace else that this missing encapsulate method will be included in 2.2.5?

Yes, its being added.
Its a bit more than just getting a Supervision report. The library at the Hubitat side should perform re-transmissions if it doesn't get a report - that's really the key. This means that if a Supervised packet to the dimmer was lost due to transmission problems, the zwave library at Hubitat should detect this and automatically perform retransmission to recover. Thus, you get a higher degree of reliability without much effort. Probably most important for secure devices like locks where you really want to be absolutely sure the command was received, but helpful overall.

1 Like

Just curious - where did you end up with this @jvm33 ?

I've been toying with this myself lately, but find no shame in leveraging others' findings - especially if we are still waiting on capability on the hub side. :slight_smile:

1 Like

I've incorporated supervision into a comprehensive codebase that I've been developing. The code provides a single driver file that supports switches, dimmers, fans, Locks, Window Shades, metering, multilevel sensors, and any of the foregoing with or without endpoints. Simply include the capabilities you want, and the driver code will adapt (it uses a number of device.hasCapability and device.hasAttribute, as well as checking supported device classes to do this). This will be released as a community driver once done.

The beta is here: HubitatCustom/Code Library at main · jvmahon/HubitatCustom · GitHub. Please give it a try. I'd be happy to explain its operation in more detail at some point.

As a simple explanation of the supervision capability, I've implemented it such that you have a simple call "sendSupervised" which does a check to see if the device actually supports the supervision class. If it does, it sends supervised. If not, it sends "regular".

I found some devices don't implement supervision properly (at least it seems that way). For example, certain devices (inovelli LZW36) seems to fail if you send supervised to an endpoint (it should work -- I've reported this problem to INovelli), while other devices do work when you send to an endpoint. But the supervision code has this handled -- every time you send supervised, it saves the command being sent (actually, it saves the last 32 commands, so it can recover). If supervision fails, the code will retry the command without supervision and marks that command as unsupervisable so it won't try that again (at least until you reboot)

Finally, another feature I"ve incorporated, is that the code checks the opensmarthouse database to learn all the parameters supported by a device and creates the parameter inputs for all the inputs.

The driver also supports up to 5 central scene taps using a new attribute "multiTapButton". The attribute takes on a decimal value X.Y where X is the button number and Y is the number of taps. So, in rule machine, you can use custom attribute "multiTapButton" and check its numeric value. For example, if you are looking for 4 taps on button 3, in Rule Machine, you trigger on the multiTapButton = 3.4 attribute value.

Install the driver, and set one of your dimmers to use it, then click "configure" or reboot to give it a try.

Thanks. I am familiar with your project - pretty cool! I just wasn't sure if you were able to get the supervised portion completely done with the changes in 2.2.5.

On a side note, the thing that would prevent me from using your driver in many cases is that I often need parameters exposed as commands for use in automations.

So as a simpler answer, yes, supervision is working with 2.2.5 and, I think, working well..

What types of parameters do you need exposed as commands? Maybe I can accommodate that too.

That's the problem with a generic solution - it depends on the specific device.

For instance, GE/Jasco motion switch/dimmer need the motion mode exposed as command(s) (param 3). Newer S2 caapble GE Enbrighten devices need Default Brightness Level (param 32). Etc.

Without making a separate database of devices with a list of what should/shouldn't be a command versus a preference, I'm not sure how that would be handled in a generic driver.

Pretty much every single user driver I use, I use because it exposes at least one parameter I need as a command... If I only needed them as preferences, I would just set them with advanced commands or basic zwave tool and use the in-box drivers.

If this is for use with Rule Machine, I suppose one solution would be to add a "setParameter" custom command that accepts two parameters - the z-wave parameter number and new value, and then in a Rule Machine you could access that by running it as a Custom Action, but then you'd still have to look up the parameter # each time you wanted to use that set Parameter custom function in Rule Machine. I may add that just in case someone needs it.

1 Like

That's an interesting idea, actually. Might be a reasonalbe workaround. Not as end user friendly as having nicely named Commands like "Manual", "Occupancy", DefaultDimmerLevel, etc - but from a technical standpoint it should work.

I would characterize it as being needed for any automation - not just RM - custom apps, Maker API, etc. But same idea.

It would be an excellent prototyping tool when designing a driver.

Its now added to the driver. So you can set Z-wave parameters either from the paremeter-specific labeled input control, or by using the set Parameter control and specifying the parameter # and value (you don't have to specify the size -- the tool gets that from opensmarthouse.org).

2 Likes

Here's another idea you might find useful, which is based on new capabilities in 2.2.5. I've found the newly added SynchronousQueues to be very helpful in getting data from a device. I use these to transfer data back from the reporting handler to the original method that initiated the get. That way, you can treat the retrieving of data much more like synchronous programming. Here's the key sample code . . .

At the top of your driver, put

import java.util.concurrent.*

Then I have a simple utility function to manage the queues. This gives me a unique queue for each command report type. I.e., if I want a queue for report class "7006" I just pass that as the parameter:

// reportQueues stores a map of SynchronousQueues. When requesting a report from a device, the report handler communicates the report back to the requesting function using a queue. This makes programming more like "synchronous" programming, rather than asynchronous handling.
// This is a map within a map. The first level map is by deviceNetworkId. Since @Field static variables are shared among all devices using the same driver code, this ensures that you get a unique second-level map for a particular device. The second map is keyed by the report class hex string. For example, if you want to wait for the configurationGet report, wait for "7006".

@Field static reportQueues = new ConcurrentHashMap<String, ConcurrentHashMap>()

SynchronousQueue myReportQueue(String reportClass)
{
	ConcurrentHashMap thisDeviceQueues = reportQueues.get(device.deviceNetworkId, new ConcurrentHashMap<String,SynchronousQueue>())
	
	// Get the queue if it exists, create (new) it if it does not.
	SynchronousQueue thisReportQueue = thisDeviceQueues.get(reportClass, new SynchronousQueue())
	return thisReportQueue
}

Within a requesting function, I then send a "Get" and can wait for the response. For example, notice in the following code that if I want a configuration value, I send the get, but then in the next line can get the response from the SynchronousQueue. I can then handle the report in the same method that requested it.

void setParameter(Map params = [parameterNumber: null , value: null ] ){
    if (params.parameterNumber.is( null ) || params.value.is( null ) ) {
		log.warn "Device ${device.displayName}: Can't set parameter ${parameterNumber}, Incomplete parameter list supplied... syntax: setParameter(parameterNumber,size,value), received: setParameter(${parameterNumber}, ${size}, ${value})."
    } else {
		parameterReports = allParameterReports.get(device.deviceNetworkId, new ConcurrentHashMap<Short, hubitat.zwave.Command>())

		Short PSize = parameterReports.get(params.parameterNumber as Short).size

		List<hubitat.zwave.Command> cmds = []
	    cmds << secure(supervise(zwave.configurationV1.configurationSet(scaledConfigurationValue: params.value as BigInteger, parameterNumber: params.parameterNumber as Short, size: PSize)))
	    cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: params.parameterNumber))
		sendToDevice(cmds)
		
		// Wait for the report that is returned after the configurationGet, and then update the input controls so they display the updated value.
		hubitat.zwave.Command report = myReportQueue("7006").poll(10, TimeUnit.SECONDS)

// Do whatever you want with the report that was just returned from the device.
		if (report)
		{
			if ((report.scaledConfigurationValue) == (params.value as BigInteger)) {
				log.info "Device ${device.displayName}: Successfully set parameter #: ${params.parameterNumber} to value ${params.value}."

				String configName = "configParam${"${report.parameterNumber}".padLeft(3,"0")}"
				if (logEnable) log.debug "Device ${device.displayName}: updating settings data for ${configName} to new value ${report.scaledConfigurationValue}!"
				device.updateSetting("${configName}", report.scaledConfigurationValue as Integer)				
	
			} else {
				log.warn "Device ${device.displayName}: Failed to set parameter #: ${params.parameterNumber} to value ${params.value}. Value of parameter is set to ${report.scaledConfigurationValue} instead."
			}
		}
    }
}

The report handler itself is then very simple. Its really just one line to transfer back the report (though I added some logging below):


void zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport  cmd)
{ 
	Boolean transferredReport = myReportQueue(cmd.CMD).offer(cmd)
	if (transferredReport) { if (logEnable) log.debug "Successfully transferred Configuration Report to waiting receiver."}
	else log.warn "Device ${device.displayName}: Failed to transfer Configuration Report."
}

As a further FYI, the drivers incorporating the Supervision code are now released on Package Manager and can be found here: GitHub - jvmahon/HubitatCustom

Look for the files: "Almost Any Z-Wave Plus Dimmer Driver.groovy" and "Almost Any Z-Wave Plus Switch Driver.groovy"

2 Likes