[RELEASE] Universal Virtual Device Type and Translator

Very cool, @Mike_Maxwell, thanks. Can you add Smoke, CO, and Flood sensor attributes to this with similar functionality.

Reason being is I have a detached garage that is far from the house. I’d like to add door sensors, motion sensors, and smoke/CO alarm and be able to integrate them with my ST hub in my house. Becuase it’s so far. it’s not possible for me to have the garage on the same ST hub that is in the house. :frowning: I have to add a second hub in the garage and use IFTTT to set and get the values of the actual devicecs in the garage by setting and getting virtual devices in ST in the house using your uDTH ie if Smoke/CO alarm goes off in garage, turn on the Virtual Smoke uDTH in the ST hub in the house in order to trigger SHM. Or use ST to turn on a Virtual Light Switch uDTH in the house and have IFTTT trigger turn on the actual light switch in the garage.

Thanks!

1 Like

Also if you add smoke/CO capabilities, that would allow ST users to use a LEEO Smart Alert and IFTTT to easily integrate their non Z-Wave smoke/CO/flood alarms into ST.

I just did that with this device handler. Granted it doesn’t show up as a smoke alarm, but I can set SHM to send me an alert, which is good enough.

I modified the code to add smoke and flood and sound sensor capabilities. Works great. Shows up as a smoke detector and flood detector. IFTTT was able to trigger it. SHM also worked with it.
Let me know if anyone wants the code.

Scott

I’d suggest just opening a pull request to Mike’s repo.

2 Likes
   /* 
	Universal virtual DTH
  	Copyright 2016 Mike Maxwell

        Modded by Scott Barton to add Smoke, Water, Sound
        Lines between the //SB and //END SB are the lines I added/modified
    
    1.0.5.  2016-08-29  added sensor and actuator capabilities per Alex
    1.0.4	2016-05-21	added LUX capability
    1.0.3	2016-05-18	added optional auto off
    1.0.2	2016-05-15	ignore duplicate input requests
    					added version info

	This software if free for Private Use. You may use and modify the software without distributing it.
 
	This software and derivatives may not be used for commercial purposes.
	You may not distribute or sublicense this software.
	You may not grant a sublicense to modify and distribute this software to third parties not included in the license.

	Software is provided without warranty and the software author/license owner cannot be held liable for damages.        

*/


metadata {
	definition (name: "UVDT", namespace: "Scott_Barton", author: "scott barton") {
		
	    capability "Sensor"
	    capability "Actuator"
		//inputs
	    capability "Switch"					//on, off
        capability "Lock"					//lock, unlock
        
        //outputs
        capability "Contact Sensor"			//"open","closed"
        capability "Motion Sensor"			//"active", "inactive"
        capability "Presence Sensor"		//"present", "not present"
        capability "Acceleration Sensor"	//"active", "inactive"
        capability "Illuminance Measurement"

    //SB
        capability "Smoke Detector"    //"detected", "clear", "tested"
        capability "Water Sensor"      //"dry", "wet"
        capability "Sound Sensor"      //"detected", "not detected"
    //END SB
        
        //both
        capability "Door Control"			//listen for "open", "close" respond with "open" "closed"
        capability "Garage Door Control"	//listen for "open", "close" respond with "open" "closed"
        
        command "localOff"
        command "localOn"
        
        attribute "version", "string"
	}
    
    preferences {
    	def s1
        def s2
        def d
        //paragraph input
        input(
            title			: "UVDT version: ${getVersion()}"
            ,description	: null
            ,type			: "paragraph"
        )
    	input(
        	title			: "Device inputs\nRespond to these events/commands." 
            ,description	: null
            ,type			: "icon"
            ,required		: false
            ,defaultValue	: "st.illuminance.illuminance.dark"
        )
        input(
        	name			: "inSwitchOn"
            ,title			: "Switch (on, off)"
           	,type			: "bool"
            ,defaultValue	: true
        )
        input(
        	name			: "inDoorOn"
            ,title			: "Door Control (open, close)"
           	,type			: "bool"
            ,defaultValue	: false
        )
        input(
        	name			: "inGDoorOn"
            ,title			: "Garge Door Control (open, close)"
           	,type			: "bool"
            ,defaultValue	: false
        )
        input(
        	name			: "inLockOn"
            ,title			: "Lock (lock, unlock)"
           	,type			: "bool"
            ,defaultValue	: false
        )
        input(
        	name			: "autoOff"
            ,title			: "Delayed device turn off (optional)"
            ,type			: "enum"
            ,required		: false
            ,options		: [["5":"5 seconds"],["30":"30 seconds"],["60":"1 Minute"],["300":"5 Minutes"]]
        )
        input( 
           	title			: "Device outputs\nSend the events listed below."
            ,description	: null
           	,type			: "icon"
            ,required		: false
            ,defaultValue	: "st.illuminance.illuminance.dark"
        )
        
        d = "Contact"
        s1 = "open"
        s2 = "closed"
        input( 
        	name			: "contactOn"
           	,title			: buildTitle(d,s1,s2)
           	,type			: "enum"
           	,options		: buildOptions(d,s1,s2)
           	,description	: contactOn ?: "Not Used, Tap to enable..."
        )        

	d = "Motion"
        s1 = "active"
        s2 = "inactive"
		input( 
        	name			: "motionOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
            ,options		: buildOptions(d,s1,s2)
            ,description	: motionOn ?: "Not Used, Tap to enable..."            
        )
        

    //SB
        d = "Smoke"
        s1 = "detected"
        s2 = "clear"
		input( 
        	name			: "smokeOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
            ,options		: buildOptions(d,s1,s2)
            ,description	: smokeOn ?: "Not Used, Tap to enable..."            
        )
        
        d = "Water"
        s1 = "wet"
        s2 = "dry"
		input( 
        	name			: "waterOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
            ,options		: buildOptions(d,s1,s2)
            ,description	: waterOn ?: "Not Used, Tap to enable..."            
        )
        
        d = "Sound"
        s1 = "detected"
        s2 = "not detected"
		input( 
        	name			: "soundOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
            ,options		: buildOptions(d,s1,s2)
            ,description	: soundOn ?: "Not Used, Tap to enable..."            
        )
    //END SB


        d = "Presence"
        s1 = "present"
        s2 = "not present"
		input( 
        	name			: "presenceOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
          	,options		: buildOptions(d,s1,s2)
            ,description	: presenceOn ?: "Not Used, Tap to enable..." 
        )
        
        d = "Door"
        s1 = "open"
        s2 = "closed"
        input( 
        	name			: "doorOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
            ,options		: buildOptions(d,s1,s2)
            ,description	: doorOn ?: "Not Used, Tap to enable..." 
        )
        d = "Acceleration"
        s1 = "active"
        s2 = "inactive"
		input( 
        	name			: "accelOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
          	,options		: buildOptions(d,s1,s2)
            ,description	: accelOn ?: "Not Used, Tap to enable..."  
        )
        d = "Illuminance"
        s1 = "0 Lux"
        s2 = "50 Lux"
		input( 
        	name			: "luxOn"
            ,title			: buildTitle(d,s1,s2)
            ,type			: "enum"
          	,options		: buildOptions(d,s1,s2)
            ,description	: accelOn ?: "Not Used, Tap to enable..."  
        )        
    }
  
  	simulator {
	}

	// tile definitions
	tiles (scale:1) {
    	multiAttributeTile(name:"switch", type: "generic", width: 6, height: 2, canChangeIcon: true){
			tileAttribute ("device.UVDT", key: "PRIMARY_CONTROL") {
				attributeState "on", label: '${name}', action: "localOff", icon: "st.switches.switch.on", backgroundColor: "#53a7c0"
				attributeState "off", label: '${name}', action: "localOn", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			}
        }
		standardTile("contact", "device.contact", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
        	state "default", label:"contact\nnot used" //, backgroundColor: "#ffffff" 
            state "closed", label:'${name}', backgroundColor: "#ffffff", icon:"st.contact.contact.closed" 
            state "open", label:'${name}', backgroundColor: "#53a7c0", icon:"st.contact.contact.open" 
		}
        standardTile("motion", "device.motion", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "motion\nnot used" //, icon:"st.motion.motion.inactive"
            state "inactive", label:'${name}', backgroundColor: "#ffffff", icon:"st.motion.motion.inactive" 
            state "active", label:'${name}', backgroundColor: "#53a7c0", icon:"st.motion.motion.active" 
        }

    //SB
        standardTile("smoke", "device.smoke", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "smoke\nnot used" //, icon:"st.alarm.smoke.clear"
            state "clear", label:'${name}', backgroundColor: "#ffffff", icon:"st.alarm.smoke.clear" 
            state "detected", label:'${name}', backgroundColor: "#e86d13", icon:"st.alarm.smoke.smoke" 
        }
        
        standardTile("water", "device.water", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "water\nnot used" //, icon:"st.alarm.water.dry"
            state "dry", label:'${name}', backgroundColor: "#ffffff", icon:"st.alarm.water.dry" 
            state "wet", label:'${name}', backgroundColor: "#53a7c0", icon:"st.alarm.water.wet" 
        }
        
        standardTile("sound", "device.sound", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "sound\nnot used" //, icon:"st.quirky.spotter.quirky-spotter-sound-off"
            state "not detected", label:'${name}', backgroundColor: "#ffffff", icon:"st.quirky.spotter.quirky-spotter-sound-off" 
            state "detected", label:'${name}', backgroundColor: "#53a7c0", icon:"st.quirky.spotter.quirky-spotter-sound-on" 
        }
    //END SB


           standardTile("present", "device.presence", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "presence\nnot used"
            state "not present", label:'${name}', backgroundColor: "#ffffff", icon:"st.presence.tile.presence-default" 
            state "present", label:'${name}', backgroundColor: "#53a7c0", icon:"st.presence.tile.presence-default" 
        }
        standardTile("door", "device.door", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "door\nnot used" 
            state "closed", label:'${name}', backgroundColor: "#ffffff", icon:"st.doors.garage.garage-closed" 
            state "open", label:'${name}', backgroundColor: "#53a7c0", icon:"st.doors.garage.garage-open" 
        }
        standardTile("accel", "device.acceleration", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "acceleration\nnot used"
            state "inactive", label:'${name}', backgroundColor: "#ffffff", icon:"st.motion.acceleration.inactive" 
            state "active", label:'${name}', backgroundColor: "#53a7c0", icon:"st.motion.acceleration.active" 
        }
        standardTile("lux", "device.lux", inactiveLabel: false, height:2, width:2, canChangeIcon: false) {
            state "default", label: "illuminance\nnot used"
            state "dark", label:'${name}', backgroundColor: "#ffffff", icon:"st.illuminance.illuminance.dark" 
            state "bright", label:'${name}', backgroundColor: "#ecf23a", icon:"st.illuminance.illuminance.bright" 
        }
        main(["switch"])
      //SB - Added "smoke","water","sound",
        details(["switch","contact","motion","smoke","water","sound","present","door","accel","lux"])
 	}
}

def open(){
	if (inDoorOn || inGDoorOn) localOn()
}

def close(){
	if (inDoorOn || inGDoorOn) localOff()
}

def lock(){
	if (inLockOn) localOn()
}

def unlock(){
	if (inLockOn) localOff()
}

def on(){
	if (inSwitchOn) localOn()
}

def off(){
	if (inSwitchOn) localOff()
}

def buildTitle(d,s1,s2){
	return "${d} (${s1}, ${s2})"
}

def buildOptions(d,s1,s2){
	def sOn = "on"
    def sOff = "off"
	def options = []
    options.add(["1":"when ${sOn} set ${d} to '${s1}'\nwhen ${sOff} set ${d} to '${s2}'"])
    options.add(["0":"when ${sOn} set ${d} to '${s2}'\nwhen ${sOff} set ${d} to '${s1}'"])
	return options
}

def syncDevices(cmd){
	if (cmd == null) cmd = device.currentValue("UVDT") == "on" ? "1" : "0"
    //log.debug "cmd: ${cmd}"
	if (contactOn != null){
		if (contactOn == cmd) sendEvent(name: "contact", value: "open")			//"when on send 'open'\nwhen off send 'closed'"
        else sendEvent(name: "contact", value: "closed")						//"when on send 'closed'\nwhen off send 'open'"
    } else sendEvent(name: "contact", value: null, displayed	: false)
	if (motionOn != null){
		if (motionOn == cmd) sendEvent(name: "motion", value: "active")			//"when on send 'active'\nwhen off send 'inactive'"
        else sendEvent(name: "motion", value: "inactive")						//"when on send 'inactive'\nwhen off send 'active'"
    } else sendEvent(name: "motion", value: null, displayed	: false)

     //SB
       if (smokeOn != null){
		if (smokeOn == cmd) sendEvent(name: "smoke", value: "detected")			//"when on send 'detected'\nwhen off send 'clear'"
        else sendEvent(name: "smoke", value: "clear")						//"when on send 'clear'\nwhen off send 'detected'"
    } else sendEvent(name: "smoke", value: null, displayed	: false)
    
    if (waterOn != null){
		if (waterOn == cmd) sendEvent(name: "water", value: "wet")			//"when on send 'wet'\nwhen off send 'dry'"
        else sendEvent(name: "water", value: "dry")						//"when on send 'dry'\nwhen off send 'wet'"
    } else sendEvent(name: "water", value: null, displayed	: false)
     
     if (soundOn != null){
		if (soundOn == cmd) sendEvent(name: "sound", value: "detected")			//"when on send 'detected'\nwhen off send 'not detected'"
        else sendEvent(name: "sound", value: "not detected")						//"when on send 'not detected'\nwhen off send 'detected'"
    } else sendEvent(name: "sound", value: null, displayed	: false) 
     //END SB

	if (presenceOn != null){
		if (presenceOn == cmd) sendEvent(name: "presence", value: "present")		//"when on send 'present'\nwhen off send 'not present'"
        else sendEvent(name: "presence", value: "not present")					//"when on send 'not present'\nwhen off send 'present'"
    } else sendEvent(name: "presence", value: null, displayed	: false)
	if (doorOn != null){
		if (doorOn == cmd) sendEvent(name: "door", value: "open")			//"when on send 'active'\nwhen off send 'inactive'"
        else sendEvent(name: "door", value: "closed")						//"when on send 'inactive'\nwhen off send 'active'"
    } else sendEvent(name: "door", value: null, displayed	: false)
	if (accelOn != null){
		if (accelOn == cmd) sendEvent(name: "acceleration", value: "active")
        else sendEvent(name: "acceleration", value: "inactive")				
    } else sendEvent(name: "acceleration", value: null, displayed	: false)
    if (luxOn != null){
    	if (luxOn == cmd){
        	sendEvent(name: "illuminance", value: 50)
        	sendEvent(name: "lux", value: "bright", displayed	: false)
        } else {
        	sendEvent(name: "illuminance", value: 0)
            sendEvent(name: "lux", value: "dark", displayed	: false)
        }
    } else {
    	sendEvent(name: "illuminance", value: null, displayed	: false)
        sendEvent(name: "lux", value: null, displayed	: false)
    }
}

def localOn() {
	if (device.currentValue("UVDT") != "on"){
    	log.info "on request: OK"
		sendEvent(name: "UVDT", value: "on" ,displayed: false)
    	syncDevices("1")
        if (autoOff) runIn(autoOff.toInteger(),localOff)
    } else {
    	log.info "on request: duplicate, ignored"
    }
}

def localOff() {
	if (device.currentValue("UVDT") != "off"){
    	log.info "off request: OK"
		sendEvent(name: "UVDT", value: "off" ,displayed: false)
    	syncDevices("0")
    } else {
    	log.info "off request: duplicate, ignored"
    }
}

def getVersion(){
	return "1.0.69"
}

//capture preference changes
def updated() {
	sendEvent( name: "version", value: getVersion(), displayed: false)
    //log.debug "syncDevices"
    syncDevices(null)
}

def configure() {

}
1 Like

I should be able to get these added to the production disto in the next few days.

4 Likes

Can you also add the ability to disable a device output that was previously enabled?
If I enable Contact accidently and save it, there is no way to disable it other than deleting the device and starting over. It would be nice to add a 3rd option ‘Not Used’ for each device output:

Contact (open, closed)
*when on set Contact to ‘open’, when off set contact to ‘closed’
*when on set contact to ‘closed’, when off set Contact to ‘open’
*Not Used

1.0.7 out, added in scotts changes, and ability to disable “not use” a formally used output capability.

5 Likes

Hi. Fairly new to ST, but excited about its expand-ability and the active Dev community. I recently implemented Mike’s Virtual Device to interface with the Roost Smart 9V. Just a couple questions:

  1. With a virtual switch like this, would there be any way to suppress the switch toggle in the mobile app, but keep the status? My goal here would be to avoid accidentally tipping the virtual switch, which in my use case reports as a smoke detector. I’d still want Roost (via IFTTT) to be able to ‘flip’ the switch, but just avoid unintended manual activation in the mobile app. Thoughts?

  2. I’m no sure yet (waiting to receive my Roost battery) whether Roost can (or will) turn the switch off when the Roost alert is cleared. If for some reason Roost doesn’t turn the switch off when cleared, is there any way to work around this in the DTH itself?

Thanks.

I’m using this dth in a similar way to get smoke alerfs in ST with Leeo via ifttt.

Why are you concerned about this? I haven’t personally found that accidentally turning switches on/off in the mobile app is an issue.

The DTH will allow you to set the switch to turn off after a few seconds (you can configure how long up to several minutes).

1 Like

NOTE: The issue that I needed help with initially has been resolved. It was just a glitch in the Device Handler code that needed to be tweaked, and I don’t need this for that anymore. Still curious though, as to whether or not I understand this SmartApp.

Is it possible to get new options added to this?

If so, I’m interested in using some old rules and custom commands that I have in Rule Machine that I created for my Rheem water heater. However, the Rheem API has changed, and they don’t work anymore.

Somebody has picked up the ball on the Rheem water heater Device Handler and Smart App to make them work with the new Rheem API, but now we need some help with the custom commands so that we can use it in rules.

If I understand it correctly (WARNING: I probably don’t lol ), the command being used in the old custom commands (and the old API) is setHeatingSetpoint whereas the new API uses setSetPoint.

Is there any way of getting this thing to translate from one command to the other? Am I even understanding what this is for? …and if so, is this even what I actually need here?

So first off major kudos to @Mike_Maxwell for this DTH. It’s super useful and is saving my bacon in getting my CoRE stuff built to overcome Presence issues.

Questions I had is there a possible way to set the device image vs icons similar to say mobile devices as presence sensors?

Also, on the My Home, Family TAB in the ST iOS app, the presence outputs show up but have no labels. Anyway to get text in there?

Thanks!

Ios mobile app is faulty, I can’t fix that. Device image can only be set for the main tile, to my knowledge individual button icons can’t be changed that way.

@Mike_Maxwell - this is a great device. I thought I saw you mention early in the thread about adapting it to a physical device. I assume that, were it possible to change the displayed tiles on the fly, you would have done it here?

I’m doing something similar for an Aeotec Dry Contact since it’s sort of the physical embodiment of a virtual ‘do-anything’ device. Again - great work!

What I was messing with was seeing if a dth could subscribe to the events of another device.
Well is can, but only one device. In any event I beleive this functionality is now or will soon be disabled by st.
There is no reason this code couldn’t be used with an actual device were the actual device parse methods added to it.
Yea, the device tile icon can’t be changed via code.

nevermind…I finally figured it out.
Wow! Thanks, @Mike_Maxwell
This is even better than I was thinking.

@Mike_Maxwell
Do you ever accept or want donations for all of the work you do here?

Hi there, is it possible to lock a device from manual control? So if I make a virtual door sensor, set it so that I can’t just press ON and simulate motion? Same with door sensors, sometimes I hit on by mistake and it starts the alarm saying door opened while alarm on. Wondering if it can be locked to prevent this?

Does anyone know if it is possible to make my uDTH not show up as every possible type of device and therefore appear as a presence sensor in the family tab?

You have to edit it. Tiles are not dynamic in a dth, bummer I know.