New smart app to link the virtual/simulated garage door device with two actual devices

I did something similar using pstuarts original Generic Camera Device code. Combining it with this smart app. I’m repurposing an old Conbrov camera that doesn’t natively support streaming outside of their app or a complicated setup with a third party server. It does however have a snapshot.cgi so I can retrieve an image using the “Take” function.

Screenshot_20190819-102925_SmartThings

The combined code is below:

/**
 *  Z-Wave Garage Door Opener w/ Camera (Customized)
 *
 *  Copyright 2014 SmartThings
 *
 *  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: "Simulated Garage Door Opener w/ Camera (Custom)", namespace: "Smartthings", author: "fieldsjm", vid: "generic-contact-2", ocfdevicetype: "oic.d.garagedoor", mnmn: "SmartThings") {
		capability "Actuator"
		capability "Door Control"
    capability "Garage Door Control"
		capability "Contact Sensor"
		capability "Refresh"
		capability "Sensor"
		capability "Health Check"
    
    capability "Image Capture"
		attribute "hubactionMode", "string"
	}

    preferences {
    input("CameraIP", "string", title:"Camera IP Address", description: "Please enter your camera's IP Address", required: true, displayDuringSetup: true)
    input("CameraPort", "string", title:"Camera Port", description: "Please enter your camera's Port", defaultValue: 80 , required: true, displayDuringSetup: true)
    input("CameraPath", "string", title:"Camera Path to Image", description: "Please enter the path to the image", defaultValue: "/SnapshotJPEG?Resolution=640x480&Quality=Clarity", required: true, displayDuringSetup: true)
    input("CameraAuth", "bool", title:"Does Camera require User Auth?", description: "Please choose if the camera requires authentication (only basic is supported)", defaultValue: true, displayDuringSetup: true)
    input("CameraPostGet", "string", title:"Does Camera use a Post or Get, normally Get?", description: "Please choose if the camera uses a POST or a GET command to retreive the image", defaultValue: "GET", displayDuringSetup: true)
    input("CameraUser", "string", title:"Camera User", description: "Please enter your camera's username", required: false, displayDuringSetup: true)
    input("CameraPassword", "string", title:"Camera Password", description: "Please enter your camera's password", required: false, displayDuringSetup: true)
	}
  
	simulator {
		
	}

	tiles {
		standardTile("toggle", "device.door", width: 2, height: 2) {
			state("closed", label:'${name}', action:"door control.open", icon:"st.doors.garage.garage-closed", backgroundColor:"#00A0DC", nextState:"opening")
			state("open", label:'${name}', action:"door control.close", icon:"st.doors.garage.garage-open", backgroundColor:"#e86d13", nextState:"closing")
			state("opening", label:'${name}', icon:"st.doors.garage.garage-closed", backgroundColor:"#e86d13")
			state("closing", label:'${name}', icon:"st.doors.garage.garage-open", backgroundColor:"#00A0DC")
			
		}
		standardTile("open", "device.door", inactiveLabel: false, decoration: "flat") {
			state "default", label:'open', action:"door control.open", icon:"st.doors.garage.garage-opening"
		}
		standardTile("close", "device.door", inactiveLabel: false, decoration: "flat") {
			state "default", label:'close', action:"door control.close", icon:"st.doors.garage.garage-closing"
      }
    standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
      state "take", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
      state "taking", label:'Taking', action: "", icon: "st.camera.take-photo", backgroundColor: "#53a7c0"
      state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
      }
    standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") {
      state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
      }
    standardTile("blank", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
      state "blank", label: "", action: "", icon: "", backgroundColor: "#FFFFFF"
      }
    carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }
		
    main "toggle"
		details(["toggle", "open", "close", "take", "blank", "refresh", "cameraDetails"])
	}
}

def parse(String description) {
	log.trace "parse($description)"
  def map = [:]
	def retResult = []
	def descMap = parseDescriptionAsMap(description)
	//Image
	def imageKey = descMap["tempImageKey"] ? descMap["tempImageKey"] : descMap["key"]
	if (imageKey) {
		storeTemporaryImage(imageKey, getPictureName())
    }
}
//one second delay (default is 6)
def open() {
	sendEvent(name: "door", value: "opening")
    runIn(1, finishOpening)
}
//one second delay (default is 6)
def close() {
    sendEvent(name: "door", value: "closing")
	runIn(1, finishClosing)
}

def finishOpening() {
    sendEvent(name: "door", value: "open")
    sendEvent(name: "contact", value: "open")
}

def finishClosing() {
    sendEvent(name: "door", value: "closed")
    sendEvent(name: "contact", value: "closed")
}

def installed() {
	log.trace "Executing 'installed'"
	initialize()
}

def updated() {
	log.trace "Executing 'updated'"
	initialize()
}

def take() {
	def userpassascii = "${CameraUser}:${CameraPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host).toUpperCase() //thanks to @foxxyben for catching this
    def porthex = convertPortToHex(CameraPort).toUpperCase()
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = CameraPath 
    log.debug "path is: $path"
    log.debug "Requires Auth: $CameraAuth"
    log.debug "Uses which method: $CameraPostGet"
    
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
   	if (CameraAuth) {
        headers.put("Authorization", userpass)
    }
    
    log.debug "The Header is $headers"
    
    def method = "GET"
    try {
    	if (CameraPostGet.toUpperCase() == "POST") {
        	method = "POST"
        	}
        }
    catch (Exception e) { // HACK to get around default values not setting in devices
    	settings.CameraPostGet = "GET"
        log.debug e
        log.debug "You must not of set the perference for the CameraPOSTGET option"
    }
    
    log.debug "The method is $method"
    
    try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: method,
    	path: path,
    	headers: headers
        )
        	
    hubAction.options = [outputMsgToS3:true]
    log.debug hubAction
    hubAction
    }
    catch (Exception e) {
    	log.debug "Hit Exception $e on $hubAction"
    }
    
}

def parseDescriptionAsMap(description) {
description.split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}

private initialize() {
	log.trace "Executing 'initialize'"

	sendEvent(name: "DeviceWatch-DeviceStatus", value: "online")
	sendEvent(name: "healthStatus", value: "online")
	sendEvent(name: "DeviceWatch-Enroll", value: [protocol: "cloud", scheme:"untracked"].encodeAsJson(), displayed: false)
}

private getPictureName() {
	def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
    log.debug pictureUuid
    def picName = device.deviceNetworkId.replaceAll(':', '') + "_$pictureUuid" + ".jpg"
	return picName
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
    return hex

}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    log.debug hexport
    return hexport
}

private Integer convertHexToInt(hex) {
	Integer.parseInt(hex,16)
}


private String convertHexToIP(hex) {
	log.debug("Convert hex to ip: $hex") 
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private getHostAddress() {
	def parts = device.deviceNetworkId.split(":")
    log.debug device.deviceNetworkId
	def ip = convertHexToIP(parts[0])
	def port = convertHexToInt(parts[1])
	return ip + ":" + port
}

To Install, follow instructions above to setup the garage components first.

Camera settings will be customized using the settings button (gear icon) in the device itself. Refer to the original camera post for community help/tips/hints on how to setup different camera types.

Awesome! Thanks!
P.

@Lgkahn Would it be possible to add a second sensor to this code? My use is for a horizontal gate and instead of relying on a single contact sensor to let me know the gate is closed I would like to be able to have two sensors one that says the gate is open and the other to tell me it’s fully closed.

It would get rid of the timed check function and say opening or closing until the two switches transmit their position open or closed…? Is this possible? I have already got this up and running and it works great for the gate on one sensor, I even have custom images and will share the finished result, but I need help adding the second contact sensor to give me the assurance that the gate is in the correct position and properly display the opening/closing feedback.

Any help would be greatly appreciated.

This is exactly why I am looking for a second contact sensor option… @joshuairl is describing the one limitation with using a contact sensor for this application but if you have 2 contact sensors one NO and the other NC then you could accurately display the position of the gate.

Without adjusting too much of the smartapp and DH code, you could utilize webcore to incorporate both sensors. Build a rule that looks at the status of both sensors to set the status of a simulated contact sensor (i.e. sensor A is open, sensor B is closed then gate sensor is open). That simulated contact sensor would be the one used in the smartapp as is.

Doesn’t help much with the opening/closing feedback but it’s a start especially since everything is functional for you at the moment.

try these,

Can someone explain the time fields please and where in the code they are set and which one means what? When I first set it up, from the time I pressed the open button to the door actually opening was quite long and also the state show in app was switching straightaway from opening to open where as door takes approximately 22 seconds to open. I tried @mark_cockcroft code and it’s opening straightaway but the it’s still going from opening to open fairly quickly and even quicker for closing to closed. Isn’t there a way to link/sync this status to the actual status of the contact sensor for closing at least. Keep showing closing till contact sensor reports closed and for opening set a desired time in the code? Thanks

Bit technical for me but I’ll give it a go. So what is the functionality of your current code? What is the expected behavior time wise and opening closing status showing wise? Where is the setting in code which defines how long before door starts opening after pressing the button?

update the app and drop this in inbtween lines 107 & 108
log.debug “GATE event - sensoropen = {evt.device} and {evt.value} = closed”
then do live loging operate the gate, look for the log debug GATE event, post that here

edit, just update the app the ‘$’ keeps getting missed of here so added it for you

Ok just opened & closed it, closing seems perfect with state changing in the device screen to closed as soon as it is closed and remains in state closing till it’s closed but opening needs some tweaking as there is no physical sensor to check, maybe put a time somewhere to change it from opening to open after certain seconds, is that possible? I only have one contact sensor which makes contact when door is closed. Is that the ideal place to put it?
9:45:46 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed
9:45:00 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed

if you only have 1 sesnsor the orignal code should have worked fine for you. on the settings page, make sure the open contact sensor is blank. then post your logs for an open and close.
also ive updated the code as so ‘$’ were missing

Open contact sensor can’t be left blank though you’ve marked it as ‘optional’, had to remove that section from the code and here are the logs. Yeah but is it possible to achieve similar result for state opening to remain opening for longer without second sensor? What is the ideal position if you have only 1 sensor?

10:03:15 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed
10:02:23 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed

updated app

fixed now can be blank - make sure its blank

there should be more events log.info, log.trace, need them all to get a picute of whats going on

When opening
As soon as the sensor detects the gate open (even an inch) it will change from opening to open.
when closing
the device will show as closing until the sensor detects the gate closed at thich point it will change from closing to closed
As you have no ‘fully open’ sensor it will default to open as soon as the sesnor detects open irelevent of still in travel.
after the check interval it will check the states are as expected, ie when closing the gate IS closed and not stoped mid travel or reversed

how did you get on?

Mark I will get on my computer later this morning and put the code in and let you know! I’m in the US so it’s 7am here and I have 2 kiddos to get ready for the day!

Thanks for putting this together I will let you know how it works later today.

One note is I do have the option for a No/NC contact sensor so when the gate is open the switch will say Open when it comes in contact and then a standard NC switch when it is closed. Not sure if that will change things… I’ll dig into it this afternoon and report back! Thank you so much Sir!

Can do if need be

d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:43 PM: info checkIfActually .dewl is 25 sec… Actual sensor state Garage Door Sensor = closed, Virtual door state Garage Door Virtual = closed & closed
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:18 PM: info virtual gd event Contact/DoorState = Garage Door Virtual - contact - closed
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:18 PM: info virtual gd event Contact/DoorState = Garage Door Virtual - door - closed
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:15 PM: trace contactHandler - Garage Door Sensor - contact - closed - virtual contact is ‘open’, door is closing
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:15 PM: debug mysend Contact sensor event, virtual contact is ‘open’, door is closing, sending 'physicalgraph.app.EventWrapper@5e9f9c9b to simulated device to sync ,
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:03:15 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:57 PM: trace virtualgdcontactHandler - sending close command to Garage Door Opener the actual garage controler
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:57 PM: debug mysend sending close command to Garage Door Opener the actual garage controler ,
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:57 PM: info virtual gd event Contact/DoorState = Garage Door Virtual - door - closing
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:52 PM: info checkIfActually .dewl is 25 sec… Actual sensor state Garage Door Sensor = open, Virtual door state Garage Door Virtual = open & open
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:27 PM: info virtual gd event Contact/DoorState = Garage Door Virtual - contact - open
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:27 PM: info virtual gd event Contact/DoorState = Garage Door Virtual - door - open
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:24 PM: trace contactHandler - Garage Door Sensor - contact - open - virtual contact is ‘closed’, door is opening
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:24 PM: debug mysend Contact sensor event, virtual contact is ‘closed’, door is opening, sending 'physicalgraph.app.EventWrapper@2ccd2687 to simulated device to sync ,
d8dbee22-e2a5-4721-8073-c0a9de6c6db2 10:02:23 PM: debug GATE event - Garage Door Sensor = {evt.device} and {evt.value} = closed

ok looks like its working it should though I haven’t updated the app to include the 2nd sensor, not required I guess since I only have 1. So is it advisable to have a 2nd sensor to use as fully open or no real benefit?

you can have 2 sensors that what i thought you wanted but the main peice of information (for me) is if its open or not, not two bother how far open/fully open