Security Encapsulation
Built-in method zwaveSecureEncap handles proper security encapsulation based on what the device is granted during inclusion and if the command is expected to be encapsulated by the receiving device.
String zwaveSecureEncap(String cmd)
Supervision
Supervision is the mechanism used to ensure reception of a command by the hub or device as ACK packets alone aren't sufficient when included with S2. The nonce in S0 is requested at the beginning of each command sent, in S2 communication is requested once unless it gets out of sync. If the S2 nonce gets out of sync the original packet is probably lost.
Supervision Get handling
S2 included devices will send many commands encapsulated in a supervision get message. In the case of multi-channel devices, you will see multi-channel encapsulated -> supervision encapsulated commands. It is critical that you send a supervision response in reply to this or the device will assume the communication was a failure.
Example non-multi-channel supervisionGet handling:
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) {
hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand(commandClassVersions)
if (encapCmd) {
zwaveEvent(encapCmd)
}
sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0).format()), hubitat.device.Protocol.ZWAVE))
}
Example multi-channel supervisionGet handling:
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd, ep = 0) {
if (logEnable) log.debug "Supervision Get - SessionID: ${cmd.sessionID}, CC: ${cmd.commandClassIdentifier}, Command: ${cmd.commandIdentifier}"
hubitat.zwave.Command encapsulatedCommand = cmd.encapsulatedCommand(CMD_CLASS_VERS)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand, ep)
}
if (ep > 0) {
sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)).format()), hubitat.device.Protocol.ZWAVE))
} else {
sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0).format()), hubitat.device.Protocol.ZWAVE))
}
}
Encapsulating outgoing commands in Supervision Get
This step is optional and not expected to be used on all commands, should only be used on actuator type commands. Ex: switch binary set, switch multilevel set, etc. Using this will ensure the packet is properly received by the destination. To use this, you must track the sessionIDs and resend if no supervision report has been received. If the sessionID is not incremented the receiving device will likely drop the packet as it is seen as a duplicate.
Example non-multi-channel code:
@Field static Map<String, Map<Short, String>> supervisedPackets = [:]
@Field static Map<String, Short> sessionIDs = [:]
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionReport cmd) {
if (logEnable) log.debug "supervision report for session: ${cmd.sessionID}"
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
if (supervisedPackets["${device.id}"][cmd.sessionID] != null) { supervisedPackets["${device.id}"].remove(cmd.sessionID) }
unschedule(supervisionCheck)
}
void supervisionCheck() {
// re-attempt once
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
supervisedPackets["${device.id}"].each { k, v ->
if (logEnable) log.debug "re-sending supervised session: ${k}"
sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(v), hubitat.device.Protocol.ZWAVE))
supervisedPackets["${device.id}"].remove(k)
}
}
Short getSessionId() {
Short sessId = 1
if (!sessionIDs["${device.id}"]) {
sessionIDs["${device.id}"] = sessId
return sessId
} else {
sessId = sessId + sessionIDs["${device.id}"]
if (sessId > 63) sessId = 1
sessionIDs["${device.id}"] = sessId
return sessId
}
}
hubitat.zwave.Command supervisedEncap(hubitat.zwave.Command cmd) {
if (getDataValue("S2")?.toInteger() != null) {
hubitat.zwave.commands.supervisionv1.SupervisionGet supervised = new hubitat.zwave.commands.supervisionv1.SupervisionGet()
supervised.sessionID = getSessionId()
if (logEnable) log.debug "new supervised packet for session: ${supervised.sessionID}"
supervised.encapsulate(cmd)
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
supervisedPackets["${device.id}"][supervised.sessionID] = supervised.format()
runIn(5, supervisionCheck)
return supervised
} else {
return cmd
}
}
Example non-multi-channel usage:
zwaveSecureEncap(supervisedEncap(cmd).format())
Example multi-channel code:
@Field static Map<String, Map<Short, String>> supervisedPackets = [:]
@Field static Map<String, Short> sessionIDs = [:]
void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionReport cmd, ep=0) {
if (logEnable) log.debug "supervision report for session: ${cmd.sessionID}"
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
if (supervisedPackets["${device.id}"][cmd.sessionID] != null) { supervisedPackets["${device.id}"].remove(cmd.sessionID) }
unschedule(supervisionCheck)
}
void supervisionCheck() {
// re-attempt once
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
supervisedPackets["${device.id}"].each { k, v ->
if (logEnable) log.debug "re-sending supervised session: ${k}"
sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(v), hubitat.device.Protocol.ZWAVE))
supervisedPackets["${device.id}"].remove(k)
}
}
Short getSessionId() {
Short sessId = 1
if (!sessionIDs["${device.id}"]) {
sessionIDs["${device.id}"] = sessId
return sessId
} else {
sessId = sessId + sessionIDs["${device.id}"]
if (sessId > 63) sessId = 1
sessionIDs["${device.id}"] = sessId
return sessId
}
}
hubitat.zwave.Command supervisedMCEncap(hubitat.zwave.Command cmd, ep = 0) {
if (ep == 0) { return supervisedEncap(cmd) }
if (getDataValue("S2")?.toInteger() != null) {
hubitat.zwave.commands.supervisionv1.SupervisionGet supervised = new hubitat.zwave.commands.supervisionv1.SupervisionGet()
supervised.sessionID = getSessionId()
if (logEnable) log.debug "new supervised packet for session: ${supervised.sessionID}"
supervised.encapsulate(cmd)
hubitat.zwave.Command epEncapped = zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(supervised)
if (!supervisedPackets."${device.id}") { supervisedPackets."${device.id}" = [:] }
supervisedPackets["${device.id}"][supervised.sessionID] = epEncapped.format()
runIn(5, supervisionCheck)
return epEncapped
} else {
return zwave.multiChannelV4.multiChannelCmdEncap(sourceEndPoint: 0, bitAddress: 0, res01: 0, destinationEndPoint: ep).encapsulate(cmd)
}
}
Example multi-channel usage:
zwaveSecureEncap(supervisedEncap(cmd, ep).format())
This will also handle the multi-channel encapsulation.