Combine Lock and Contact in single device

Could you include the full file for the code you're using? The error messages you have there are referring to things that are not in the snipets you provided. For example. you're getting an error about util.toJson... but I don't see what util is or where it is defined.

Here is the full driver (to the best of my re-edit abilities since I'm off-site for a couple days and don't have VPN access setup yet):

/**
 *  Copyright 2017 Eric Maycock
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
metadata {
	definition (name: "Verilock Translator", namespace: "erocm123", author: "Eric Maycock", vid:"generic-lock") {
		capability "Refresh"
        capability "Lock"
        capability "Contact Sensor"
		capability "Configuration"
		capability "Sensor"
		capability "Zw Multichannel"
        
        fingerprint mfr: "0178", prod: "5A44", model: "414E"
	}

}

def parse(String description) {
	def result = null
	if (description.startsWith("Err")) {
	    result = createEvent(descriptionText:description, isStateChange:true)
	} else if (description != "updated") {
		def cmd = zwave.parse(description, [0x20: 1, 0x84: 1, 0x98: 1, 0x56: 1, 0x60: 3])
		if (cmd) {
			result = zwaveEvent(cmd)
		}
	}
	log.debug("'$description' parsed to $result")
	return result
}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
	[ createEvent(descriptionText: "${device.displayName} woke up", isStateChange:true),
	  response(["delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]) ]
}

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd, ep = null)
{
    def evtName
    def evtValue
    switch(cmd.event){
        case 1:
            evtName = "lock"
            evtValue = "locked"
        break;
        case 2:
            evtName = "lock"
            evtValue = "unlocked"
        break;
        case 22:
            evtName = "contact"
            evtValue = "open"
        break;
        case 23:
            evtName = "contact"
            evtValue = "closed"
        break;
    }
    def childDevice = childDevices.find{it.deviceNetworkId == "$device.deviceNetworkId-ep${ep}"}
    if (!childDevice) {
        log.debug "Child not found for endpoint. Creating one now"
        childDevice = addChildDevice("Lockable Door/Window Child Device", "${device.deviceNetworkId}-ep${ep}", null,
                [completedSetup: true, label: "${device.displayName} Window ${ep}",
                isComponent: false, componentName: "ep$ep", componentLabel: "Window $ep"])
    }

    childDevice.sendEvent(name: evtName, value: evtValue)
        
    def allLocked = true
    def allClosed = true
    childDevices.each { n ->
       if (n.currentState("contact") && n.currentState("contact").value != "closed") allClosed = false
       if (n.currentState("lock") && n.currentState("lock").value != "locked") allLocked = false
    }
    def events = []
    if (allLocked) {
       sendEvent([name: "lock", value: "locked"])
    } else {
       sendEvent([name: "lock", value: "unlocked"])
    }
    if (allClosed) {
       sendEvent([name: "contact", value: "closed"])
    } else {
       sendEvent([name: "contact", value: "open"])
    }
}

private List loadEndpointInfo() {
	if (state.endpointInfo) {
		state.endpointInfo
	} else if (device.currentValue("epInfo")) {
		fromJson(device.currentValue("epInfo"))
	} else {
		[]
	}
}

def updated() {
    childDevices.each {
        if (it.label == "${state.oldLabel} ${channelNumber(it.deviceNetworkId)}") {
		    def newLabel = "${device.displayName} ${channelNumber(it.deviceNetworkId)}"
			it.setLabel(newLabel)
        }
	}
	state.oldLabel = device.label
}

private channelNumber(String dni) {
	dni.split("-ep")[-1] as Integer
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd) {
	updateDataValue("endpoints", cmd.endPoints.toString())
	if (!state.endpointInfo) {
		state.endpointInfo = loadEndpointInfo()
	}
	if (state.endpointInfo.size() > cmd.endPoints) {
		cmd.endpointInfo
	}
	state.endpointInfo = [null] * cmd.endPoints
	//response(zwave.associationV2.associationGroupingsGet())
	[ createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:""),
	  response(zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: 1)) ]
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) {
	def result = []
	def cmds = []
	if(!state.endpointInfo) state.endpointInfo = []
	state.endpointInfo[cmd.endPoint - 1] = cmd.format()[6..-1]
	if (cmd.endPoint < getDataValue("endpoints").toInteger()) {
		cmds = zwave.multiChannelV3.multiChannelCapabilityGet(endPoint: cmd.endPoint + 1).format()
	} else {
		log.debug "endpointInfo: ${state.endpointInfo.inspect()}"
	}
	result << createEvent(name: "epInfo", value: util.toJson(state.endpointInfo), displayed: false, descriptionText:"")
	if(cmds) result << response(cmds)
	result
}

def zwaveEvent(hubitat.zwave.commands.associationv2.AssociationGroupingsReport cmd) {
	state.groups = cmd.supportedGroupings
	if (cmd.supportedGroupings > 1) {
		[response(zwave.associationGrpInfoV1.associationGroupInfoGet(groupingIdentifier:2, listMode:1))]
	}
}

def zwaveEvent(hubitat.zwave.commands.associationgrpinfov1.AssociationGroupInfoReport cmd) {
	def cmds = []
	/*for (def i = 0; i < cmd.groupCount; i++) {
		def prof = cmd.payload[5 + (i * 7)]
		def num = cmd.payload[3 + (i * 7)]
		if (prof == 0x20 || prof == 0x31 || prof == 0x71) {
			updateDataValue("agi$num", String.format("%02X%02X", *(cmd.payload[(7*i+5)..(7*i+6)])))
			cmds << response(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:num, nodeId:zwaveHubNodeId))
		}
	}*/
	for (def i = 2; i <= state.groups; i++) {
		cmds << response(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:i, nodeId:zwaveHubNodeId))
	}
	cmds
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x32: 3, 0x25: 1, 0x20: 1])
	if (encapsulatedCommand) {
		if (state.enabledEndpoints.find { it == cmd.sourceEndPoint }) {
			def formatCmd = ([cmd.commandClass, cmd.command] + cmd.parameter).collect{ String.format("%02X", it) }.join()
			createEvent(name: "epEvent", value: "$cmd.sourceEndPoint:$formatCmd", isStateChange: true, displayed: false, descriptionText: "(fwd to ep $cmd.sourceEndPoint)")
		} else {
			zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
		}
	}
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x84: 1])
	if (encapsulatedCommand) {
		state.sec = 1
		def result = zwaveEvent(encapsulatedCommand)
		result = result.collect {
			if (it instanceof hubitat.device.HubAction && !it.toString().startsWith("9881")) {
				response(cmd.CMD + "00" + it.toString())
			} else {
				it
			}
		}
		result
	}
}

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd) {
	def versions = [0x31: 2, 0x30: 1, 0x84: 1, 0x9C: 1, 0x70: 2]
	// def encapsulatedCommand = cmd.encapsulatedCommand(versions)
	def version = versions[cmd.commandClass as Integer]
	def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
	def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
	if (encapsulatedCommand) {
		zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	createEvent(descriptionText: "$device.displayName: $cmd", isStateChange: true)
}

def configure() {
	commands([
		zwave.multiChannelV3.multiChannelEndPointGet()
	], 800)
}

def epCmd(Integer ep, String cmds) {
	def result
	if (cmds) {
		def header = state.sec ? "988100600D00" : "600D00"
		result = cmds.split(",").collect { cmd -> (cmd.startsWith("delay")) ? cmd : String.format("%s%02X%s", header, ep, cmd) }
	}
	result
}

def enableEpEvents(enabledEndpoints) {
	state.enabledEndpoints = enabledEndpoints.split(",").findAll()*.toInteger()
	null
}

private command(hubitat.zwave.Command cmd) {
	if (state.sec) {
		zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
	} else {
		cmd.format()
	}
}

private commands(commands, delay=200) {
	delayBetween(commands.collect{ command(it) }, delay)
}

private encap(cmd, endpoint) {
	if (endpoint) {
		command(zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint).encapsulate(cmd))
	} else {
		command(cmd)
	}
}

private encapWithDelay(commands, endpoint, delay=200) {
	delayBetween(commands.collect{ encap(it, endpoint) }, delay)
}

And here is the Child driver code (which essentially has nothing much after deleting the Tiles section from ST):

/**
 *  Lockable Door/Window Child Device
 *
 *  Copyright 2017 Eric Maycock
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
metadata {
	definition (name: "Lockable Door/Window Child Device", namespace: "erocm123", author: "Eric Maycock", vid:"generic-lock") {
		capability "Contact Sensor"
        capability "Lock"
		capability "Sensor"
	}



}

Could you please use the code formatter (the </> button in the toolbar)? That's really hard to read. You should be able to edit your post and highlight the text then click the button.

1 Like

Done. Much better. Yet again I learned something new! Thanks.

2 Likes

I'll take a look later this evening. By the way these devices are super cool. I wish they weren't $70 a piece. I have too many windows for my wife to be OK with that!

1 Like

One last thing, when you loaded it in as Device, could you please go to the device and paste the part that looks like:

Actually if you could paste a screenshot, plus that inClusters part as text (so I don't make a typo retyping it!) that'd be great. Think I'm making progress, just have no way to test

I'll probably have to do that on Thursday night. I'm off site now and haven't had time to sort out a good VPN method so I can only see the dashboards at the moment.

Gotcha, well I think I have it to a point where it would work, but without that data section it won't autodetect, it'd still pair as Device. I'll upload the code to github tonight and you can test on Thursday.

Give this a shot

When you can get me that data section I'll update it. If you run into any issues let me know but I think I fixed the 3 errors you reported.

1 Like

Here is the Data section:

inClusters: 0x5E,0x86,0x70,0x8E,0x85,0x59,0x7A,0x71,0x22,0x5A,0x73,0x72,0x60

Cool... so moment of truth, does the driver I provided work? :slight_smile:

Was just trying it as I pasted that. It gets farther than the previous driver, but I'm still getting some errors:

dev:1622020-05-14 08:49:10.054 pm debug'zw device: 1A, command: 600D, payload: 0B 00 71 05 00 00 00 FF 09 FE 00 C4 00 , isMulticast: false' parsed to null

dev:1622020-05-14 08:49:09.748 pm debugChild not found for endpoint. Creating one now

dev:1622020-05-14 08:49:03.329 pm errorjava.lang.StringIndexOutOfBoundsException: String index out of range: 7 on line 143 (parse)

dev:1622020-05-14 08:49:03.161 pm debug'zw device: 1A, command: 6008, payload: 80 09 C4 00 , isMulticast: false' parsed to [[name:epInfo, value:[null,null,null,null,null,null,null,null,null], displayed:false, descriptionText:], 600901]

Got a little time to try to debug? I just checked in a little debug logging

FYI for anyone following, @dtbrewer helped test and I have this working now. Information is availabe at [RELEASE] Andersen Verilock Translator Driver

I'm looking to combine a lock and context into a single device, exactly as the thread topic suggests. The drivers in the thread seem to refer to a specific Andersen product, Verilock. Has anyone successfully combined a lock device and a contact device into a single lock/contact device. My primary purpose is to have one tile on a dashboard that indicates both attributes.

Even if you create such a device, there is no dashboard tile that will display it like that. You'd need to create a custom tile using HTML with something like SuperTile

Totally prepared to do that. I have other tiles that show 2 attributes, like temp and motion.

Ok good. Then this is really something that would require groovy coding to take care of. You would need an app that allows you to pick both the lock and contact and then a device that supports both capabilities.

I remember that @mike.maxwell had a universal device handler for SmartThings that had lots capabilities and devices - including lock and contact sensor. But I can't find it on GitHub any more.

I would also check the ST forums - there maybe another device handler similar to it that you could port to Hubitat.

2 Likes