Combine Lock and Contact in single device

Not sure if this is the best method but wondering if anyone has done this.

I'm looking to combine my lock and open/close contact on a single device so I don't have to have 2 buttons on the dashboard. My thought was to create a virtual device and then when the lock is locked/unlocked, update the virtual device via RM. Not an issue. The problem is when the contact sensor opens/closes, I need to update the contact of the virtual device. Can't find this anywhere in RM.

I have a Yale Andersen Assure lock that reports Z-Wave. The z-wave reports to HE fine that the lock is locked/unlocked. No problem there. The lock also has a contact sensor built in. That transmits to a Verilock translator which then connects via Z-wave to HE. The contact sensor reports fine too. The problem lies in now I have 2 different devices in HE for the same door and I'm trying to figure out how to make them a single device. Anyone?

You can set virtual contact sensors in RM under "Run Custom Action."

But then you can't set Lock/Unlock on a virtual contact sensor. I've tried using a virtual lock but then you can't set Open/Close. No matter what type of device I set for the virtual... I don't see a way to do both Lock/Unlock and also Open/Close.

I have devices that do both... like my andersen windows. Shows both Open/Close and Lock/Unlock. I'm just unable to build a virtual device that does both.

Instead of doing that, have you looked into Tile Master or (when Cobra's apps come back) Super Tile? You can combine attributes from multiple devices into one tile, saving you some space on Dashboard and effectively achieving what you want--which I'm actually not sure how you plan to with a single device right now anyway, given that the stock dashboard templates would still limit you to displaying only one attribute (either contact or lock) from the device.

Alternatively, you could consider something like this: if your Dashboard is currently, for example, 6 tiles wide by 5 tiles tall, consider making it 6x10 instead of 6x5. Then, specify all of your "normal" tiles to take up 2 rows instead of the default of 1. This should make it look more or less like it did before (you may need to adjust font settings to your liking). Then, for your lock and contact sensor tiles, create two as normal but make them 1 row tall in the same column. They'll look half-height in comparison--a neat trick you can use to achieve something like you want without the need for custom code.

Actually, I'm using SharpTools, not HE dashboards. They have the capability to show dual values on a tile. For example my window sensors report both open/close and lock/unlock so on the dashboard I can show both Closed and Locked on a single tile.
image

1 Like

Ok... got it to work. Created a new virtual lock/contact driver using the below coding. I was then able to create rules to update the virtual lock when the yale lock changes and also update the virtual lock when the z-wave contact opens/closes. I can now show both or either state on a single device.

metadata {
definition (name: "Virtual Lock Contact", namespace: "hubitat", author: "Sparling") {
capability "Contact Sensor"
capability "Lock"

    command "open"
    command "close"
    command "lock"
    command "unlock"
  
}
preferences {
    input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
}

}

def installed() {
log.warn "installed..."
close()
}

def updated() {
log.info "updated..."
log.warn "description logging is: ${txtEnable == true}"
}

def parse(String description) {
}

def lock() {
def descriptionText = "${device.displayName} was locked"
if (txtEnable) log.info "${descriptionText}"
sendEvent(name: "lock", value: "locked", descriptionText: descriptionText)
}

def unlock() {
def descriptionText = "${device.displayName} was unlocked"
if (txtEnable) log.info "${descriptionText}"
sendEvent(name: "lock", value: "unlocked", descriptionText: descriptionText)
}

def open() {
def descriptionText = "${device.displayName} was opened"
if (txtEnable) log.info "${descriptionText}"
sendEvent(name: "contact", value: "open", descriptionText: descriptionText)
}

def close() {
def descriptionText = "${device.displayName} was closed"
if (txtEnable) log.info "${descriptionText}"
sendEvent(name: "contact", value: "closed", descriptionText: descriptionText)
}

1 Like

@asparling78
Does this mean you have been able to get the Andersen Verilock Translator to work with Hubitat?
If so could you share how you did it please? I'm starting the switch over from Wink and have a number of those sensors connected to my translator I would like to move over to Hubitat.

Thanks

Just asking again if you could share anything on how you made the Andersen Translator work with HE?

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