Help with first MultiChannel and child device driver

I am porting a driver over from ST and everything seems to be working but I really like to understand what all the code is doing. This multiChannelCmdEncap stuff is one thing that is throwing me off. Part of the issue is all the back and forth between the hub and device, make it all more confusing. There is some sort of a workaround in this code also, which again seems to be working and I can actually see what it is doing in my command trace logging. But why... is this really a zwave bug or just a bad implementation that needed a workaround?

Other things is the Parent / child stuff. From what I can gather the child is basically just there so the extra "channel" (which is a second button on this switch) can be added to the dashboard as a switch and used in rules. So any of the events that happen on that channel I should send to the child. Some of the code I ported over just seems like it might be un-necessary but I don't know enough to determine that exactly.

I think I have all the other stuff pretty well nailed down from the other similar ports I did, and I also compared it heavily to the example Hubitat drivers and another driver for the same switches.

Any feedback, links to articles, other well made drivers I can peek at, whatever is appreciated. I can learn from reading instructions or looking at other code.

What is that driver supposed to provide for the Zen30 that the built in driver (since 2.2 i think) doesn't?

The built in one does not support the CentralScene button presses, or association groups, and it also does not have all the parameters available if I recall from my testing. If I could get a peek at some official Hubitat code handling the MultiChannelCmdEncap that would probably help me a lot. Didn't see anything in the examples. I am looking at a driver from a similar Inovelli switch and they dont seem to have this workaround in theirs, so maybe this workaround is a Zooz bug not a general Zwave thing.

Check the function on line 605 for the workaround code I am talking about

void zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
	logTrace "${cmd}"
	// Workaround that was added to all SmartThings Multichannel DTHs.
	if (cmd.commandClass == 0x6C && cmd.parameter.size >= 4) { // Supervision encapsulated Message
		// Supervision header is 4 bytes long, two bytes dropped here are the latter two bytes of the supervision header
		cmd.parameter = cmd.parameter.drop(2)
		// Updated Command Class/Command now with the remaining bytes
		cmd.commandClass = cmd.parameter[0]
		cmd.command = cmd.parameter[1]
		cmd.parameter = cmd.parameter.drop(2)
		logTrace "FIXED: ${cmd}"
	}

And then the output in the logs you can see what it is doing.

Zooz Double Switch: FIXED: MultiChannelCmdEncap(bitAddress:false, command:3, commandClass:37, destinationEndPoint:0, parameter:[0], res01:false, sourceEndPoint:1)

Zooz Double Switch: MultiChannelCmdEncap(bitAddress:false, command:1, commandClass:108, destinationEndPoint:0, parameter:[43, 3, 37, 3, 0], res01:false, sourceEndPoint:1)

I don't like leaving dead end posts in case anyone else has the same question later.

I figured out the proper way to handle this multichannel stuff instead of that workaround that was in there. Excuse me if I don't know the correct terms but the command is basically double encapsulated, final command encapped in Supervision, encapped in Multichannel.

So I get rid of the workaround in the MultiChannel event handler and out comes a Supervision command. We have to pass the endpoint to the supervision handler function but it was not setup for it. So I just added the parameter with a default of 0. Then the supervision handler has to pass the endpoint to its final destination. I had to then also add another parameter to the CentralScene function so it would not complain, but it is always 0 since those do not come across multichannel for this device.

Here is the gist of it (plus adding the extra endpoint=0 parameter to anything that might get a command from supervision)

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

void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, endpoint=0) {
	logTrace "${cmd} (${endpoint})"
	def encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
	logTrace "${encapsulatedCmd}"
	
	if (encapsulatedCmd) {
		zwaveEvent(encapsulatedCmd, endpoint)
	} 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))
}

I'm currently trying to get a better understanding of how Supervision works. Two things:

  1. For anybody else reading this, the Supervision command is documented in Silicon Labs standard SDS13783 (https://www.silabs.com/documents/login/miscellaneous/SDS13783-Z-Wave-Transport-Encapsulation-Command-Class-Specification.pdf)
  2. I'm not sure about the addition of the endpoint = 0 in your code. As I understand what is happening, when you call the cmd.encapsulatedCommand function, it strips off the Supervision bytes, and leaves you with the underlying "supervised" command. So, when you then call zwaveEvent() in the SupervisionGet handler, shouldn't you just call it with the underlying command without specifying an endpoint? Then, if the underlying command is a multichannel command, it will get to the handler for MultiChannelCmdEncap which is the one responsible for figuring out the proper endpoint (and, in turn, sending zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer) from that stage to the handler for the underlying multichannel encapsulated command. So, in your code, shouldn't
if (encapsulatedCmd) {
		zwaveEvent(encapsulatedCmd, endpoint)

just be:

if (encapsulatedCmd) {
		zwaveEvent(encapsulatedCmd)

As I said, I'm just figuring this out myself, but I'm pretty sure this is how it should work (but not 100% sure, so if you think I missed something in the standards, please let me know). Thanks!

Also, as an FYI - I'm currently working on a "universal" driver designed to handle all switches / all dimmers / locks. The code is written so that it all supports multichannel (if the device does), but works just as well with non-multichannel devices and works for any device class version supported by the device. If you want examples on how MultiChannel works, or at least on how to set up code in a "universal" fashion, you could take a look at the driver here: https://github.com/jvmahon/HubitatCustom. Its also available on Hubitat Package Manager

I think it would work as-is for your Zen30, but I haven't tested it on that device. You'd have to install the driver, click "initialize" then "deleteChildDevices" to clear out the old child devices, and then "createChildDevices" to create new ones. This process will eventually be automated, but the driver is still in beta / development -- I'm working on adding Supervision when commands are sent to nodes in order to increase reliability / guarantee delivery.

I will check out that docs and your code when I get a chance. I have not had much luck in finding good clear examples of the proper way to do this. The way it was working on this ZEN30 though, it is sending the multiChannelCmdEncap from the device which has the sourceEndpoint in it. Once I de-encapsulate? it, I am getting a Supervision command which does not have an endpoint value in it, so I need to pass that endpoint along to supervision as a separate argument, which then passes it to the final destination such as SwitchBinary. Maybe I could just jam the endpoint number back into the new command before I pass it to supervision as well, that would get rid of the extra arguments.

Figure 1 (page 12) of that document gives a good example of how to layer the different encapsulations. See below. Given that figure, and how multichannel is typically handled, my guess is that, rather than endpoint = 0, you might want endpoint = null. Then when the multichannel command de-encapsulates it, it will provide the endpoint to the supervision command (if there is an endpoint). Also, I think you might need to add the endpoint as a parameter to secure in the sendHubCommand. This is how endpoints typically work in my code and it seems to be successful for both secured and unsecured nodes. So, if you find that your original code doesn't work quite right, then here's a suggestion which follows from examples in my code, it would look like:

void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, endpoint = null ) {
	logTrace "${cmd} (${endpoint})"
	def encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
	logTrace "${encapsulatedCmd}"
	
	if (encapsulatedCmd) {
		zwaveEvent(encapsulatedCmd, endpoint)
	} 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), endpoint), hubitat.device.Protocol.ZWAVE))
}

And here's the figure I mentioned . . .

For months now, I cant get the Zen30 to function as a scene controller. When I press the upper or lower paddle, it just sends the on or off command as shown in events. It does not send the button press to events unless I initiate the press in the device page on hubitat.

I want to be able to turn off multiple lights when the lower paddle is pressed on the ZEN30 using RM. What i expected was when the lower paddle is pressed is events showing that device is off and button 2 was pressed. This was the case until Hubitat's update broke this.

I was directed to your driver by a rep from "The Smartest House". She advised to exclude the ZEN30 and reinclude the ZEN30 after installing your driver because she said just switching the driver wont work. The problem is that when I reinclude the ZEN30, it defaults to the default driver and switching the driver seems to loose features from the old Hubitat device page on the ZEN 30.

I am not sure how to correctly install your driver so the ZEN30 works correctly. Any advise would be appreciated. Thanks!!

Here is the release post for the driver. I think the step you are missing is to press the configure button after switching to my driver. Then refresh the page after it is done, you should get all the settings back.

Thank you!! The configure button did the trick!

So I had a chance to dig into @jvm33's comments finally. I just want to explain it here a little if anyone else finds this. I did read the appropriate sections on those docs which gives some good insight as well. I think he was assuming the secureCmd function in my code would handle the endpoint and then multichannel encapsulate it, which is what the code in his driver does. Mine is a little different, there is a separate function that adds the multichannel first.

So the gist of it is, that if you are handling a SupervisionGet according to SPEC you MUST reply back which is what the supervisionReport is for. It is basically telling the device that you got its message and acted on it. You can also report back different statuses explained in those docs. Anyway, the reply back for multichannel devices should be multichannel encapsulated if the destination is != 0. So in my code I changed it to be like this below which will add the multichannelEncap if the endpoint is !=0, otherwise it sends the command as-is to the securityCmd function. (I have changed some other functions as well and the full new code will be posted to GitHib today probably.)

sendCommands(multiChannelCmdEncapCmd(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0),endpoint))

I think you are correct and I need to add the multiChannelCmdEncapCmd when ep != 0.

Actually you already have it in there, you just built it into the same function that secures the message, where my code has it in two functions. Looks like this is doing it already to me.

String secure(String cmd, ep = null){ 
	if (ep) {
		return zwaveSecureEncap(zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint: ep).encapsulate(cmd))
	} else {
		return zwaveSecureEncap(cmd) 
	}
}
1 Like

So I did - gets hard to keep track sometimes!