Virtual Switches and Dual Relays

How do you connect a virtual switch to a dual relay? I have several vision relay's and dual relay's throughout the house and I need to setup a virtual switch for switch1/switch2 on the dual relays. I'm using the monoprice driver that I ported from ST and I created a virtual switch, but I don't see the way to connect them.

  1. Install the Driver Code from Bruce. Device Polling
  2. Create two virtual switches, one for each of the relay’s.
  3. Create custom commands in Rule Machine
    a. Click Rule Machine in Apps
    b. Custom Commands
    c. Select capability for test device: switch
    d. Choose a relay to use
    e. Click New custom command
    f. Select on or off 1 or 2
    g. Click Save command now
    h. Click Done, repeat from f for the other 3, click done again.
  4. Create new Rule Machine Rule
  5. For the Actions(True/False) select Run custom commands, choose what you want.

This requires 4 rules to cover on/off for both relays. Probably an easier way, but I’m trying to familiarize myself with RM.

Eric Maycock’s Virtual Device Sync App and Devices don’t work. The virtual switches are created but don’t process any commands. Child/Parent thing I’m guessing?

1 Like

Here’s what I’ve done, and where I’m stuck or confused.

  1. Installed dual relay driver (monoprice)
  2. Setup the dual relay (Family Room Dual Relay)
  3. Created VS-1 (Family Room Deck Light)
  4. Created VS-2 (Master Bedroom Outside Light)

Then…

I went into RM and have done the following

  1. Create Custom commands for on1, off1, on2, off2 tested against the relay
  2. Create Rule (Family Room Deck Light - On)
  3. Custom command True -> on1 on Family Room Dual Relay
  4. Same but reversed with Fale -> off1 on Family Room Dual Relay

Now what? What is the purpose of the Virtual Switches? Where do they tie in?

Status:

I went through all of the steps, and the ones I listed. It was the rule machine part that wasn’t getting and so I did some playing. Here’s what I did.

  1. Created a trigger
  • Trigger Event
    • VS is turned on
  • Trigger Action
    • on1 against relay

Did that twice one trigger for ON and one for OFF.

Testing from the devices I go to my VS and click on - light turns on. click off - light turns off. Manually turn light on or off the VS status does NOT change. The relay device itself does change status.

I probably did the RM stuff wrong.

Oh yeah, VS status change, forgot about that, my family almost NEVER uses the actual switches.

That’d be another two rules to have the VS’s switched, each. Let me think on this, must be a better way.

My family still hits the switches. So status changes are a must for at the very least the all important Bathroom switches!!!

I have a light automation in the bathroom and if that got screwed up because of them using the switch… man… I don’t know if you’d hear from me anymore.

1 Like

I can’t choose “Physical Switch” for a custom capability, I could have sworn that existed under RM in ST, unless I’m mixing it with CoRE…

Without that, we can’t get the state of the relay’s independently I don’t think.

@bravenel can you help guide us on this? Either I’m over thinking it, or the option I remember is a false memory!

Physical switch is available for triggers in RM. It is not available for a rule. The reason is that physical switch is an event, not a state. You can set up a trigger that responds to the physical switch being turned on, and take actions accordingly. You could also have a triggered rule, where the physical switch event then causes a rule evaluation. There are not such things as “custom capabilities” in RM.

Ah ok. So that’s a problem then. When I select physical switch and then the relay, I get no option for WHICH switch so I can’t determine which is actually physically triggered.

I’m guessing we’ll need to get the virtual device sync app\drivers working, but that won’t happen until parent\child is implemented. Sounds about right?

Not sure what Hubitat is doing in the background, but with other systems dual relays show up as independent switches. In Home Assistant, OpenHAB and ST for each of my relays I end up with 3 switches. Switch, Switch1, Switch2 and Switch/Switch1 are tied together. But the point is that in the zwave backend they are individual switches.

Can you post a screen shot of the device details page for the device?



1 Like

Guys, not sure you guys know but I am using the dual relay ST smartapps and able to tie 2 virtual switches to the dual relay just like ST. Here's the link to the post from ST with the app in there.

This doesn’t work at all for me. The relay device updates instantly when I trigger either physical switch, great!

But neither assigned virtual switches do anything. And more worrisome is the relay device page doesn’t register on/off consistently, polling or refreshing sometimes updates the status but even that is flaky.

What did you do to get it working?

I am using this driver Device Polling - #15 by bravenel

and apps


/**
 *  Dual Relay Adapter (i.e. Enerwave ZWN-RSM2 Adapter, Monoprice Dual Relay, Philio PAN04)
 *
 *  Copyright 2014 Joel Tamkin
 *
 *	2015-10-29: erocm123 - I removed the scheduled refreshes for my Philio PAN04 as it supports instant
 *	status updates with my custom device type
 *
 *  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.
 *
 */
definition(
    name: "joel Dual Relay Adapter",
    namespace: "",
    author: "Joel Tamkin",
    description: "Associates Dual Relay Switch Modules with one or two standard SmartThings 'switch' devices for compatibility with standard control and automation techniques",
    category: "My Apps",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")


preferences {
  section("ZWN-RSM2 Module:") {
    input "rsm", "capability.switch", title: "Which RSM2 Module?", multiple: false, required: true
    input "switch1", "capability.switch", title: "First Switch?", multiple: false, required: true
    input "switch2", "capability.switch", title: "Second Switch?", multiple: false, required: false
  }
}

def installed() {
  log.debug "Installed!"
  //subscribe(rsm, "switch", rsmHandler)
  subscribe(rsm, "switch1", rsmHandler)
  subscribe(rsm, "switch2", rsmHandler)
  subscribe(switch1, "switch", switchHandler)
  subscribe(switch2, "switch", switchHandler)

  initialize()
}

def updated() {
  log.debug "Updated!"
  unsubscribe()
  subscribe(rsm, "switch", rsmHandler)
  subscribe(rsm, "switch1", rsmHandler)
  subscribe(rsm, "switch2", rsmHandler)
  subscribe(switch1, "switch", switchHandler)
  subscribe(switch2, "switch", switchHandler)
  
  initialize()
}

def switchHandler(evt) {
  //log.debug "switchHandler: ${evt.value}, ${evt.deviceId}, ${evt.source}, ${switch2.id}"
  switch (evt.deviceId) {
  	case switch1.id:
		switch (evt.value) {
        	case 'on':
        		log.debug "switch 1 on"
                rsm.on1()
                break
        	case 'off':
        		log.debug "switch 1 off"
                rsm.off1()
                break
            }
        break
    case switch2.id:
    	switch (evt.value) {
        	case 'on':
        		log.debug "switch 2 on"
                rsm.on2()
                break
        	case 'off':
        		log.debug "switch 2 off"
                rsm.off2()
                break
            }
        break

    default:
    	pass
  }
}

def rsmHandler(evt) {
	log.debug "$evt.name $evt.value"
    if (evt.name == "switch1") {
    	switch (evt.value) {
        	case 'on':
            	switch1.on()
                break
            case 'off':
            	switch1.off()
                break
        }
    }
    else if (evt.name == "switch2") {
    	switch (evt.value) {
        	case 'on':
            	switch2.on()
                break
            case 'off':
            	switch2.off()
                break
        }
    }
      	
}

def rsmRefresh() {
	rsm.refresh()
}
    
def initialize() {
    unschedule()
    //def exp = "* * * * * ?"
    //log.debug "Scheduling RSM refreshes"
	//schedule(exp, rsmRefresh)
    // TODO: subscribe to attributes, devices, locations, etc.
}

So I finally had time to come back to this as I've migrated more dual relay's over. I get no status update on the dual relay device itself. And as I noticed before, setting a trigger in RM won't work because it only seems the "main" switch.

I've double checked I'm using the right code, and also went back to Joel's app vs Eric's, neither work though.

Are you really getting updates on the physical switch on/off of either relay? Poll and Refresh on the device don't do anything, well Poll gives me a null thing...

Sorry for the late reply. I don't really use the physical switches for these dual relays. I created 2 virtual switches and use Joel app to tie them to the dual relay.

Has anyone modified this dual relay driver to utilize the new child device functions? I would love to eliminate the app that ties a virtual switch to the 2nd relay.

So I have modified some code I found around the forums that uses parent/child device drivers to handle all my Monoprice dual relays in case it helps others. No more need for a smart app or virtual switches and once the children are created they can be renamed to anything you like.

/*
 *  Monoprice/Vision Dual Relay Parent Driver
 */
metadata {
    definition (name: "Dual Relay Driver", namespace: "hubitat", author: "hubitat") {
        capability "Refresh"
        capability "Actuator"
        
        command "childOn"
        command "childOff"
        command "recreateChildDevices"
        command "deleteChildren"

        //fingerprint manufacturer: "015D", prod: "0651", model: "F51C", deviceJoinName: "Zooz ZEN20 Power Strip"
        //fingerprint deviceId: "0x1004", inClusters: "0x5E,0x85,0x59,0x5A,0x72,0x60,0x8E,0x73,0x27,0x25,0x86"
   }
}

def installed() {
    log.debug "installed"
    createChildDevices()
    configure()
}

def updated() {
    log.debug "updated"
    
    if (!childDevices) {
        createChildDevices()
    }
    else if (device.label != state.oldLabel) {
        childDevices.each {
            def newLabel = "$device.displayName (CH${channelNumber(it.deviceNetworkId)})"
            it.setLabel(newLabel)
        }
  
        state.oldLabel = device.label
    }

    configure()
}

def configure() {
    log.debug "configure"
    def cmds = [
                   zwave.versionV1.versionGet().format(),
                   zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
                   zwave.firmwareUpdateMdV2.firmwareMdGet().format()
               ]
    response(delayBetween(cmds, 1000))
}

def refresh() {
    log.debug "refresh"
    def cmds = []
    cmds << zwave.basicV1.basicGet().format()
    cmds << zwave.switchBinaryV1.switchBinaryGet().format()
    
    (1..2).each { endpoint ->
        cmds << encap(zwave.basicV1.basicGet(), endpoint)
        cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), endpoint)
    }
 
    delayBetween(cmds, 100)
}

def recreateChildDevices() {
    log.debug "recreateChildDevices"
    deleteChildren()
    createChildDevices()
}

def deleteChildren() {
	log.debug "deleteChildren"
	def children = getChildDevices()
    
    children.each {child->
  		deleteChildDevice(child.deviceNetworkId)
    }
}


def parse(String description) {
    log.debug "parse $description"
    def result = null
 
    if (description.startsWith("Err")) {
        result = createEvent(descriptionText:description, isStateChange:true)
    } else {
        def cmd = zwave.parse(description, [0x60: 3, 0x25: 1, 0x20: 1])
        log.debug "Command: ${cmd}"
  
        if (cmd) {
            result = zwaveEvent(cmd)
            log.debug "parsed '${description}' to ${result.inspect()}"
        } else {
            log.debug "Unparsed description $description"
        }
    }
 
    result
}


def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
    log.debug "multichannelv3.MultiChannelCmdEncap $cmd"
    def encapsulatedCommand = cmd.encapsulatedCommand([0x25: 1, 0x20: 1])
    log.debug "encapsulatedCommand: $encapsulatedCommand"
 
    if (encapsulatedCommand) {
        return zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
    } else {
        log.debug "Unable to get encapsulated command: $encapsulatedCommand"
        return []
    }
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd, endpoint = null) {
    log.debug "basicv1.BasicReport $cmd, $endpoint"
    zwaveBinaryEvent(cmd, endpoint, "digital")
}

def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd, endpoint = null) {
    log.debug "switchbinaryv1.SwitchBinaryReport $cmd, $endpoint"
    zwaveBinaryEvent(cmd, endpoint, "physical")
}

def zwaveBinaryEvent(cmd, endpoint, type) {
    log.debug "zwaveBinaryEvent cmd $cmd, endpoint $endpoint, type $type"
    def childDevice = childDevices.find{it.deviceNetworkId.endsWith("$endpoint")}
    def result = null
 
    if (childDevice) {
        log.debug "childDevice.sendEvent $cmd.value"
        childDevice.sendEvent(name: "switch", value: cmd.value ? "on" : "off", type: type)
    } else {
        result = createEvent(name: "switch", value: cmd.value ? "on" : "off", type: type)
    }
 
    result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
    log.debug "manufacturerspecificv2.ManufacturerSpecificReport cmd $cmd"
    updateDataValue("MSR", String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId))
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
    log.debug "configurationv2.ConfigurationReport: parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
    log.debug "configurationv2.ConfigurationReport: parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd, endpoint) {
   log.debug "versionv1.VersionReport, applicationVersion $cmd.applicationVersion, cmd $cmd, endpoint $endpoint"
}

def zwaveEvent(hubitat.zwave.Command cmd, endpoint) {
    log.debug "${device.displayName}: Unhandled ${cmd}" + (endpoint ? " from endpoint $endpoint" : "")
}

def zwaveEvent(hubitat.zwave.Command cmd) {
    log.debug "${device.displayName}: Unhandled ${cmd}"
// My Code
  runIn( 1, refresh )
///My Code
}

def on() {
    log.debug "on"
    def cmds = []
    cmds << zwave.basicV1.basicSet(value: 0xFF).format()
    cmds << zwave.basicV1.basicGet().format()
    
    (1..2).each { endpoint ->
        cmds << encap(zwave.basicV1.basicSet(value: 0xFF), endpoint)
        cmds << encap(zwave.basicV1.basicGet(), endpoint)
    }

    return delayBetween(cmds, 1000)
}

def off() {
    log.debug "off"
    def cmds = []
    cmds << zwave.basicV1.basicSet(value: 0x00).format()
    cmds << zwave.basicV1.basicGet().format()
    
    (1..2).each { endpoint ->
        cmds << encap(zwave.basicV1.basicSet(value: 0x00), endpoint)
        cmds << encap(zwave.basicV1.basicGet(), endpoint)
    }
    
    return delayBetween(cmds, 1000)
}

def childOn(String dni) {
    onOffCmd(0xFF, channelNumber(dni))
}

def childOff(String dni) {
    onOffCmd(0, channelNumber(dni))
}

private onOffCmd(value, endpoint) {
    log.debug "onOffCmd, value: $value, endpoint: $endpoint"
    
    def cmds = []
    cmds << encap(zwave.basicV1.basicSet(value: value), endpoint)
    cmds << encap(zwave.basicV1.basicGet(), endpoint)
    
    return delayBetween(cmds, 1000)
}

private channelNumber(String dni) {
    def ch = dni.split("-")[-1] as Integer
    return ch
}

private void createChildDevices() {
    log.debug "createChildDevices"
    
    for (i in 1..2) {
        addChildDevice("hubitat", "Dual Relay Driver (Child)", "$device.deviceNetworkId-$i", [name: "ch$i", label: "$device.displayName $i", isComponent: true])
    }
}

private encap(cmd, endpoint) {
    if (endpoint) {
        zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint).encapsulate(cmd).format()
    } else {
        cmd.format()
    }
}
/*
 *  Monoprice/Vision Dual Relay Child Device Driver
 */
metadata {
    definition (name: "Dual Relay Driver (Child)", namespace: "hubitat", author: "hubitat") {
        capability "Switch"
        capability "Actuator"
    }
}

void on() { 
    log.debug "$device on"
    parent.childOn(device.deviceNetworkId)
}

void off() {
    log.debug "$device off"
    parent.childOff(device.deviceNetworkId)
}

Once you change the device driver click the "Recreate Children" and it should make your two devices one for each relay.

Here is what it looks like when your done and they show up as individual switches in RM and other apps.

12 Likes

I tried this on my Enerwave Dual Relay's, it doesn't respond. The ST Monoprice and Enerwave were basically interchangeable. Any idea what would be different with your modified one?

I also tried to rejoin my Monoprice ones, but HE won't finish finding them, I've had a nightmare of a time with some z-wave devices joining for some reason. They join right back up to ST no problem, go figure.

Edit: Magically the one Enerwave started working! It's quicker than the old driver too. I think it's showing active status more accurately as well from physical switches. But it's still a big lagged. Switch one always worked better even for ST and that behavior is similar here. Looks like I don't need my RM rule to refresh anymore at least!

Thanks for doing this!