Generic Camera Device using local connection (new version now available)

The only other website I usually try is the link below.
https://www.ispyconnect.com/man.aspx?n=Tenvis

1 Like

Can you get the JPG/snapshot page to work from your computer? If you cant get that working then dont move into the Smartthings side. I find a search of modelname+snapshot works well in Google to get hints, and then aided by removing any authentication settings on the camera while testing.
C

Yes.

The URL isā€¦
http://IPADDRESS/tmpfs/auto.jpg

ā€¦and the jpg comes up just fine when I go to that URL in a browser.

And if you use this what appears in the debug logs? (After changing the IP address to a correct IP)

Hi all.

Iā€™ve read through the entire thread and understand that ST keep removing undocumented access and changing API calls. The work, and value, of the community is hugely apparent in this and why ST havenā€™t implemented this functionality natively is a surprise.

What I havenā€™t been able to really work out is - Is this currently working or not?

I manage to get to a point where my logs show:

d22db97a-ed33-486f-9f9d-10380dde758a  9:48:57 PM: debug 486b581197b145c4a08ac15b2a50e310
d22db97a-ed33-486f-9f9d-10380dde758a  9:48:57 PM: debug Parsing 'index:19, mac:000DC5D951AF, ip:C0A8010F, port:1F4F, requestId:777e61da-f8e3-4b85-b0c6-637cda64e501, tempImageKey:b0c0ce16-0ce2-4451-bb9d-a6b64e398bba'

which would imply that the issue is with storing to S3. Iā€™ve tried an array of cameras so I donā€™t think it is image size (which varies from camera to camera about 28k-43k)

Is there further debugging or testing I can do to prove itā€™s the storage element?

thanks

1 Like

Any chance youā€™d be willing to combine this one, with the newer one that can stream? I have the newer one working great, but Iā€™d like to be able to take a single frame-grab when something happensā€¦ Either from the RTSP or from a JPG URL in addition to the stream URL.

Thoughts?

Feel free to fork it. I have ended my support for these DTHs so if anyone wants to pick up and fork the github and combine the two, feel free.

Once AGAIN Patrick RULES ! I just updated to the new code and it works again.

Hey SMARTTHINGS - WAKE UP and stop hosing your devleopers. WIthout them, you have no product.

1 Like

Hi there,
I tried this thing, on a small website, that has nothing except deliver that .jpg file, and the thing is I donā€™t see anything on the network flowing from the hub to the HTTP website. is there anything I need to activate, to have the hub support this hubaction thing and finally send those request.

and by the way I agree with all of you, the documentation is just crappy. and provides absolutely no clear guidance for a beginner.

This is meant for lan only. Use httpget for internet requests.

Thanks Patrick,
Iā€™m actually trying to get it work on LAN :slight_smile:

since nothing was working, I decided to give it a try from Internet with HTTPGet, which worked :slight_smile:

but for now I need this crappy thing to work in LAN. with this really not explicit hubaction, this seems to me almost impossible.

Iā€™m sure as an absolute novice, I might be missing something really obviousā€¦ but I canā€™t seem to find what :slight_smile:

Anyone know why iā€™m getting this error?
error Error creating device: physicalgraph.app.exception.UnknownDeviceTypeException: Device type ā€˜Generic Video Cameraā€™ in namespace ā€˜pstuartā€™ not found.

Any ability (or plans) for motion sensing triggers?

Iā€™m hoping @pstuart, @RBoy, and/or anyone else can come up with a DH for Sercomm 8221. Hereā€™s some API information I found already but donā€™t know how to integrate it into ST:

Custom vendor reset and API access:

API documentation details:

All models video URLs:
https://www.ispyconnect.com/man.aspx?n=Sercomm

@pstuart Is there a reason I couldnt merge this with the video device handler? That way I can see the video feed while on my local connection, and then when I am away from home I can still press ā€˜Takeā€™ in order to get a snapshot of what is going on.

I love the fact that I can automate the ā€˜Takeā€™ function through CoRE, so I dont want to lose that, but presumably it could sit there as a secondary function to the DH without any issues? I just thought I would quickly ask the question before making a start, in case there were any words of wisdom that I should take note of!

Thanks in advance :slight_smile:

Anything is possible, feel free to fork or submit a pull request against it the repo. I have discontinued development of smartthings related code for the time being.

1 Like

Yes I saw that happening with all the changes they introduced! Even now it is incredibly frustrating getting code to work with nigh on zero documentation about large swathes of the features!

I will give it a go and put in the request based on how successfully it works! :slight_smile:

I was playing around with pstuartā€™s generic camera handlers and managed to combine them. If anyone is interested, itā€™s pasted below. I literally copied the video handler code into the photo handler and tweaked it a bit. I take no credit, except for my excellent copy and pasting.

/** 
 *  
 *  Generic IP Camera
 *
 *  Based on pstuart's Generic Video Camera and Generic Camera Device
 *
 *  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: "Generic IP Camera", namespace: "tinlau", author: "tinlau") {
		capability "Image Capture"
		capability "Sensor"
		capability "Actuator"
		capability "Configuration"
		capability "Video Camera"
		capability "Video Capture"
		capability "Refresh"
		capability "Switch"
        
        command "start"
        
		attribute "hubactionMode", "string"
	}

    preferences {
    input("CameraIP", "string", title:"Camera IP Address", description: "Please enter your camera's IP Address", required: false, displayDuringSetup: true)
    input("CameraPort", "string", title:"Camera Port", description: "Please enter your camera's Port", defaultValue: 80 , required: false, displayDuringSetup: true)
    input("CameraPath", "string", title:"Camera Path to Image", description: "Please enter the path to the image", defaultValue: "/snapshot.jpg", required: false, displayDuringSetup: true)
    input("CameraPathVideo", "string", title:"Camera Path to Video", description: "Please enter the path to the video", defaultValue: "/stream/getvideo", required: false, 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, required: false, 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", required: false, 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 {
		multiAttributeTile(name: "videoPlayer", type: "videoPlayer", width: 6, height: 4) {
			tileAttribute("device.switch", key: "CAMERA_STATUS") {
				attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
				attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
				attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
				attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
			}

			tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
				attributeState("errorMessage", label: "", value: "", defaultState: true)
			}

			tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
				attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
				attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
				attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
				attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
			}

			tileAttribute("device.startLive", key: "START_LIVE") {
				attributeState("live", action: "start", defaultState: true)
			}

			tileAttribute("device.stream", key: "STREAM_URL") {
				attributeState("activeURL", defaultState: true)
			}	
		}
		
    
        standardTile("take", "device.image", width: 2, 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", width: 2, height: 1, inactiveLabel: false, decoration: "flat") {
            state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
        }
        standardTile("blank", "device.image", width: 2, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
            state "blank", label: "", action: "", icon: "", backgroundColor: "#FFFFFF"
        }
        carouselTile("cameraDetails", "device.image", width: 6, height: 4) { }
        
        main "take"
        details(["videoPlayer", "take", "blank", "refresh", "cameraDetails"])
    }
}

//for photo
def parse(String description) {
    log.debug "Parsing '${description}'"
    def map = [:]
	def retResult = []
	def descMap = parseDescriptionAsMap(description)
	//Image
	def imageKey = descMap["tempImageKey"] ? descMap["tempImageKey"] : descMap["key"]
	if (imageKey) {
		storeTemporaryImage(imageKey, getPictureName())
	} 
}

// handle commands
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 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
}

//for video
private getVideoURL() {
    def videoURL = ""
    if (CameraAuth) {
    	videoURL = "http://${CameraUser}:${CameraPassword}@${CameraIP}:${CameraPort}${CameraPathVideo}"
    } else {
    	videoURL = "http://${CameraIP}:${CameraPort}${CameraPathVideo}"
    }
    return videoURL
}

mappings {
   path("/getInHomeURL") {
       action:
       [GET: "getInHomeURL"]
   }
}

def installed() {
	configure()
}

def updated() {
	configure()
}

// parse events into attributes
/*def parse(String description) {
	log.debug "Parsing '${description}'"
}*/

// handle commands
def configure() {
	log.debug "Executing 'configure'"
    sendEvent(name:"switch", value: "on")
}

def start() {
	log.trace "start()"
    def videoURL = getVideoURL()
    log.debug videoURL
	def dataLiveVideo = [
		OutHomeURL  : videoURL,
		InHomeURL   : videoURL,
		ThumbnailURL: "http://cdn.device-icons.smartthings.com/camera/dlink-indoor@2x.png",
		cookie      : [key: "key", value: "value"]
	]

	def event = [
		name           : "stream",
		value          : groovy.json.JsonOutput.toJson(dataLiveVideo).toString(),
		data		   : groovy.json.JsonOutput.toJson(dataLiveVideo),
		descriptionText: "Starting the livestream",
		eventType      : "VIDEO",
		displayed      : false,
		isStateChange  : true
	]
	sendEvent(event)
}

def getInHomeURL() {
	 [InHomeURL: getVideoURL()]
}
2 Likes

That works a treat for me! You beat me to it! Thanks @tinlau

In case it is useful for anybody reading, I have a Trendnet camera and the URLs are below for picture and video:
/streaming/channels/1/picture
/streaming/channels/1/httpPreview

With @tinlauā€™s version, video only works if you are physically on the same LAN as the camera. This because the live video is actually being rendered by your mobile device, and not by the hub (unlink the still snapshot, which IS taken by the hub over the local LAN). If youā€™re arenā€™t on the same LAN, youā€™ll get a ā€˜cannot connectā€™ error when you try to run live video.

I fixed this by adding (optional) support for specifying the DNS address and Port ID of the camera (in addition to the local IP address and local port). This way, if you have port forwarding configured for your camera (manually or via UPnP), you can get the live video remotely as well. The implementation will (supposedly) use the local IP if on the same LAN, or use the external DNS address if not.

/** 
 *  
 *  Generic IP Camera
 *
 *  Based on pstuart's Generic Video Camera and Generic Camera Device
 *
 *  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: "Generic IP Camera", namespace: "tinlau", author: "tinlau") {
		capability "Image Capture"
		capability "Sensor"
		capability "Actuator"
		capability "Configuration"
		capability "Video Camera"
		capability "Video Capture"
		capability "Refresh"
		capability "Switch"
        
        command "start"
        
		attribute "hubactionMode", "string"
	}

    preferences {
	    input("CameraIP", "string", title:"Camera internal IP Address", description: "Enter your camera's local IP Address", required: false, displayDuringSetup: true)
		input("CameraPort", "string", title:"Camera internal Port", description: "Enter your camera's local Port", defaultValue: 80 , required: false, displayDuringSetup: true)
		input("CameraDNS", "string", title:"External DNS Address (optional)", description: "Enter your camera's external DNS Address", defaultValue: '', required: false, displayDuringSetup: true)
		input("CameraEPort", "string", title:"External Port (optional)", description: "Enter your camera's external Port", defaultValue: '', required: false, displayDuringSetup: true)
    	input("CameraPath", "string", title:"Camera Path to Image", description: "Please enter the path to the image", defaultValue: "/snapshot.jpg", required: false, displayDuringSetup: true)
    	input("CameraPathVideo", "string", title:"Camera Path to Video", description: "Please enter the path to the video", defaultValue: "/stream/getvideo", required: false, 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, required: false, 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", required: false, 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 {
		multiAttributeTile(name: "videoPlayer", type: "videoPlayer", width: 6, height: 4) {
			tileAttribute("device.switch", key: "CAMERA_STATUS") {
				attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
				attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
				attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
				attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
			}

			tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
				attributeState("errorMessage", label: "", value: "", defaultState: true)
			}

			tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
				attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
				attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
				attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
				attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
			}

			tileAttribute("device.startLive", key: "START_LIVE") {
				attributeState("live", action: "start", defaultState: true)
			}

			tileAttribute("device.stream", key: "STREAM_URL") {
				attributeState("activeURL", defaultState: true)
			}	
		}
		
    
        standardTile("take", "device.image", width: 2, 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", width: 2, height: 1, inactiveLabel: false, decoration: "flat") {
            state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
        }
        standardTile("blank", "device.image", width: 2, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
            state "blank", label: "", action: "", icon: "", backgroundColor: "#FFFFFF"
        }
        carouselTile("cameraDetails", "device.image", width: 6, height: 4) { }
        
        main "take"
        details(["videoPlayer", "take", "blank", "refresh", "cameraDetails"])
    }
}

//for photo
def parse(String description) {
    log.debug "Parsing '${description}'"
    def map = [:]
	def retResult = []
	def descMap = parseDescriptionAsMap(description)
	//Image
	def imageKey = descMap["tempImageKey"] ? descMap["tempImageKey"] : descMap["key"]
	if (imageKey) {
		storeTemporaryImage(imageKey, getPictureName())
	} 
}

// handle commands
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 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
}

//for video
private getVideoURL() {
    if (CameraDNS != '') {
    	def videoURL = ""
    	if (CameraAuth) {
    		videoURL = "http://${CameraUser}:${CameraPassword}@${CameraDNS}:${CameraEPort}${CameraPathVideo}"
    	} else {
    		videoURL = "http://${CameraDNS}:${CameraEPort}${CameraPathVideo}"
    	}
    	return videoURL
    } else {
    	return getInHomeVideoURL()
    }
}

private getInHomeVideoURL() {
    def videoURL = ""
    if (CameraAuth) {
    	videoURL = "http://${CameraUser}:${CameraPassword}@${CameraIP}:${CameraPort}${CameraPathVideo}"
    } else {
    	videoURL = "http://${CameraIP}:${CameraPort}${CameraPathVideo}"
    }
    return videoURL
}

mappings {
   path("/getInHomeURL") {
       action:
       [GET: "getInHomeURL"]
   }
}

def installed() {
	configure()
}

def updated() {
	configure()
}

// parse events into attributes
/*def parse(String description) {
	log.debug "Parsing '${description}'"
}*/

// handle commands
def configure() {
	log.debug "Executing 'configure'"
    sendEvent(name:"switch", value: "on")
}

def start() {
	log.trace "start()"
    def videoURL = getVideoURL()
    def inHomeVideoURL = getInHomeVideoURL()
    log.debug "\n${videoURL}\n${inHomeVideoURL}"
	def dataLiveVideo = [
		OutHomeURL  : videoURL,
		InHomeURL   : inHomeVideoURL,
		ThumbnailURL: "http://cdn.device-icons.smartthings.com/camera/dlink-indoor@2x.png",
		cookie      : [key: "key", value: "value"]
	]

	def event = [
		name           : "stream",
		value          : groovy.json.JsonOutput.toJson(dataLiveVideo).toString(),
		data		   : groovy.json.JsonOutput.toJson(dataLiveVideo),
		descriptionText: "Starting the livestream",
		eventType      : "VIDEO",
		displayed      : false,
		isStateChange  : true
	]
	sendEvent(event)
}

def getInHomeURL() {
	 [InHomeURL: getInHomeVideoURL()]
}

You should be able to save this on top of @tinlauā€™s version without breaking anything, then just edit the preferences to add the external DNS and Port for the camera (it will ty to use the IP address as before if you donā€™t edit the preferences).

2 Likes