Combine Lock and Contact in single device

I did. Do you still need a solution? Sorry... not logged into community often.

@dtbrewer
or if anybody else needs it, this is basically what I did.

I used SmartThingsPublic/verilock-translator.groovy at master · erocm123/SmartThingsPublic · GitHub and converted it to Hubitat. The main thing is change "physicalgraph" in the code to "Hubitat". Not hard. There might be other changes but you can look on this community if you get an error loading into Hubitat.

Then for the child device, add another device driver: SmartThingsPublic/lockable-door-window-child-device.groovy at master · erocm123/SmartThingsPublic · GitHub

Then all you need to do it pair the translator (once all your doors/windows are linked to the translator) and pair it to Hubitat. The translator needs to be assigned to Verilock Translator device type and then all the sensors will be assigned to Lockable Door/Window Child Device.

All my doors and windows are showing.

1 Like

Perfect thanks!

I'll try it out in a couple of weekends. It will be nice to have all of my door sensors on one Hub. Just have to move these door sensors and my LeakSmart ZigBee water sensors over from my Wink Hub to Hubitat. Will let you know how it works. Do you have any idea if you can pair up more sensors to the Verilock Translator after you move it over to Hubitat?

Yes. Wasn’t hard to do. I added a few more after initial sync. Worked perfectly.

Sounds good, thanks.

I got the Verilock drivers edited and loaded this weekend, but didn't have time to do the switchover from Wink. We'll see how it works for me soon. The Child driver was very short after taking out the Tiles section which isn't used for Hubitat to my understanding. I'll report back again after I try it out.

@asparling78 So I got back to this and tried to add the Verilock translator. It joined ok, but didn't find the driver and joined just as a "Device" so I changed over to the Verilock Translator driver and it doesn't appear to be working right. See debug logs below. To adapt the driver you pointed to I made 3 changes: the usual physicalgraph->hubitat, deleted the "simulator" section, deleted the "tiles" section (per App and driver porting to Hubitat).

When I try to run Configure I get the first error message about the pointer exception. When I try to run Refresh I get the other errors. (Line numbers are probably slightly off from your code due to differences in how I might have edited out the sections.)

Any ideas on what you did differently to make this work?

Thanks

Errors:
dev:1622020-05-09 04:21:38.661 pm errorjava.lang.IllegalArgumentException: deviceNetworkId is not specified. on line 72 (parse)

dev:1622020-05-09 04:21:38.637 pm debugChild not found for endpoint. Creating one now

dev:1622020-05-09 04:20:33.565 pm errorjava.lang.IllegalArgumentException: deviceNetworkId is not specified. on line 72 (parse)

dev:1622020-05-09 04:20:33.542 pm debugChild not found for endpoint. Creating one now

dev:1622020-05-09 04:18:44.640 pm errorjava.lang.IllegalArgumentException: deviceNetworkId is not specified. on line 72 (parse)

dev:1622020-05-09 04:18:44.616 pm debugChild not found for endpoint. Creating one now

dev:1622020-05-09 04:11:54.831 pm errorgroovy.lang.MissingMethodException: No signature of method: user_driver_erocm123_Verilock_Translator_394.refresh() is applicable for argument types: () values: [] Possible solutions: every(), parse(java.lang.String), every(groovy.lang.Closure), grep() (refresh)

dev:1622020-05-09 04:11:45.917 pm errorjava.lang.NullPointerException: Cannot invoke method toJson() on null object on line 132 (parse)

Here are more details, realized that it might be difficult to figure out code sections based on different edits so I copied snipets near the offending sections.

  1. This error happens throughout the day on a semi regular basis, I'm guessing there is also something wrong with my edit of the Child code (after taking the Tiles section out of the ST code there was almost nothing left).

Error1:
dev:1622020-05-10 08:40:00.967 am errorjava.lang.IllegalArgumentException: deviceNetworkId is not specified. on line 72 (parse)

dev:1622020-05-10 08:40:00.943 am debugChild not found for endpoint. Creating one now

Code in driver near that - lines 69-75:

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"])
}
  1. This error happens on selecting Configure in the device:

Error2:
dev:1622020-05-10 08:57:21.648 am errorjava.lang.NullPointerException: Cannot invoke method toJson() on null object on line 132 (parse)

Code near there lines 130 to 133:

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)) ]
  1. This error happens when hitting Refresh in the device:

Error3:
dev:1622020-05-10 09:04:12.865 am errorgroovy.lang.MissingMethodException: No signature of method: user_driver_erocm123_Verilock_Translator_394.refresh() is applicable for argument types: () values: [] Possible solutions: every(), parse(java.lang.String), every(groovy.lang.Closure), grep() (refresh)

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]