SmartThings Community

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

I did really quick… It appears the images are streaming in via this url https://smartthings-smartsense-camera.s3.amazonaws.com/{key_for_image}

I just tried this outside the app, and good news, there seems to be some authentication to the storage, so can’t access the images outside of the app, easily…

can’t get this to work with my Foscam 8910W.

Heres a good link for the foscam URLs: http://www.ispyconnect.com/man.aspx?n=foscam

But i cannot get it to pull anything from the path “/snapshot.cgi?” also tried to make the path this:
"/snapshot.cgi?user=[USERNAME]&pwd=[PASSWORD]&count=0"

Logs show it is is failing at line 183 of the device code for me.

java.lang.StringIndexOutOfBoundsException: String index out of range: 8 @ line 183

Any ideas?

Not having a foscam to test with I bet the device is using a Post instead of Get.

EDIT: I added a POST option in the settings / configuration to the latest github code

Generic Camera Device

What happens if you set author to false and hardcode the user and pass in the url?

This looks like you didn’t enter the IP address properly in the settings of the device. Make sure you enter it as 192.168.100.123 do not enter http:// or anything like that.

Hello Patrick - How exactly do I add this code to my SmartThings (hub or App)? This is all new to me, sorry if this has been covered elsewhere.

I have a Mobotix camera that has 360 degree field of view I would love to be able to integrate into ST>

You need IDE access. https://graph.api.smartthings.com/

go into my device types https://graph.api.smartthings.com/ide/devices

click + New SmartDevice

fill out just name, namespace and author and click create

copy and paste my code from the github https://github.com/pstuart/smartthings/blob/master/generic_camera.groovy

Click the save button

Click the publish button, for me

Go to My Devices

click + Add New Devices

Give it the following:
Name = anything you want
device Network Id = unique id (should be the hex IP and hex port of the device but my code will auto insert that when you set the ip and take a photo)
Type = Generic Camera Device
Version = Published
Location = your location
hub = your hub
group = what folder do you want the “thing” in, none is the default.

then click create

you should then get a page that shows you the device info

look for a preferences section with a (edit) link.

Click the edit link, add the properties for the camera: (these are one of my camera’s settings, replace with your values)

Camera IP Address: 192.168.101.249
Camera Port: 80
Camera Path to Image: /SnapshotJPEG?Resolution=640x480&Quality=Clarity
Does Camera require User Auth?: true
Does Camera use a Post or Get, normally Get?: GET
Camera User: username
Camera Password: password

click save.

Go to your app and under things you should now see a new camera thing. Tap it.

Tap the take button, should see a picture show up if everything worked.

If not, go back into the IDE https://graph.api.smartthings.com/

Go to the Logs section https://graph.api.smartthings.com/ide/logs

go back to your app and device tap the take button again and paste in the console results for the camera.

6 Likes

Yeah… there is something wrong with your string manipulations. It’s returning ‘a00001a’ for ‘10.0.0.26’ (which ain’t right).

Yep, i did. I’m a server admin by day so i understand how this stuff works

Same here. I am also getting the wrong HEX value returned. i get “a000016” for “10.0.0.22”

By the way thanks Pat for doing this!!! you rock man!

I have removed the redundant host parsing and the prefs entry for it. This will work if you simply enter the correct device network ID in the device (i.e. 0A00001A:1A2D for 10.0.0.26:6701). This uses the same hex conversion as my other device-types so I’m pretty sure it will work with any IP/port. Thanks @pstuart for the image parsing!

metadata {
	definition (name: "D-Link Camera", namespace: "soletc.com", author: "Scottin Pollock") {
		capability "Image Capture"
		capability "Sensor"
		capability "Actuator"
	}

    preferences {
    input("CameraPath", "string", title:"Camera Path to Image", description: "Please enter the path to the image", defaultValue: "/image/jpeg.cgi", 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("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("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: true) {
			state "default", label: "", action: "", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF"
		}

		carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }

		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"
		}

		main "camera"
		details(["cameraDetails", "take"])
	}
}

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

	def map = stringToMap(description)

	def result = []

	if (map.bucket && map.key)
	{ //got a s3 pointer
		putImageInS3(map)
	}
	else if (map.headers && map.body)
	{ //got device info response

		def headerString = new String(map.headers.decodeBase64())
		if (headerString.contains("404 Not Found")) {
			state.snapshot = "/snapshot.cgi"
		}

		if (map.body) {
			def bodyString = new String(map.body.decodeBase64())
			def body = new XmlSlurper().parseText(bodyString)
			def productName = body?.productName?.text()
			if (productName)
			{
				log.trace "Product Name: $productName"
				state.snapshot = ""
			}
		}
	}

	result
}

def putImageInS3(map) {

	def s3ObjectContent

	try {
		def imageBytes = getS3Object(map.bucket, map.key + ".jpg")

		if(imageBytes)
		{
			s3ObjectContent = imageBytes.getObjectContent()
			def bytes = new ByteArrayInputStream(s3ObjectContent.bytes)
			storeImage(getPictureName(), bytes)
		}
	}
	catch(Exception e) {
		log.error e
	}
	finally {
		//explicitly close the stream
		if (s3ObjectContent) { s3ObjectContent.close() }
	}
}

// handle commands
def take() {
    def userpassascii = "${CameraUser}:${CameraPassword}"
    def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def path = CameraPath //"/image/jpeg.cgi"
    log.debug "path is: $path"
    log.debug "Requires Auth: $CameraAuth"
    
    
   	if (CameraAuth) {
    	def hubAction = new physicalgraph.device.HubAction(
        method: "GET",
        path: path,
        headers: [HOST:getHostAddress(), Authorization:userpass]
        )
        
		hubAction.options = [outputMsgToS3:true]
   		hubAction
		}
    else
    {
    	def hubAction = new physicalgraph.device.HubAction(
        method: "GET",
        path: path,
        headers: [HOST:getHostAddress()]
        )
        
		hubAction.options = [outputMsgToS3:true]
    	hubAction
    	}
    }

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

private Long converIntToLong(ipAddress) {
	log.debug "from Long converIntToLong = $ipAddress"
	long result = 0
	def parts = ipAddress.split("\\.")
    for (int i = 3; i >= 0; i--) {
        result |= (Long.parseLong(parts[3 - i]) << (i * 8));
    }

    return result & 0xFFFFFFFF;
}

private String convertIPToHex(ipAddress) {
	return Long.toHexString(converIntToLong(ipAddress));
}

private Integer convertHexToInt(hex) {
log.debug 
	Integer.parseInt(hex,16)
}
private String convertHexToIP(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
}

It isn’t redundant, it just overrides the device setting since most people don’t know how to get the Hex ip and port of their device.

That code also allows you to change the ip in the settings and not have to go back into the IDE and change the deviceNetworkId since there is no way to change it in the App.

First of all, remove the [Host:… line, that contains your password… Probably don’t want that on a forum.

Second, try my latest code…

What is the ip and port you are trying to use, specifically the port, as it appears the port code to hex screwed up.

mine is 8006 for the port and it converts it correctly. mine seems to be failing at the IP conversion. :frowning:

I just updated the code, the length of the hex port was too long, for port # under 100 I have to pad 00 to the hex, but over that I have to just use the result. Should be fixed.

https://github.com/pstuart/smartthings/blob/master/generic_camera.groovy

Not having luck with this. Tried Post and Get. Entering 192.168.1.201:84400//Streaming/channels/1/picture for my camera in a browser pulls up an image after authenticating. In the device tile, after hitting “take”, it gets stuck on “taking” with no image displayed. Camera is a Hivision IP Cam.

ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug The method is GET
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug c0a801c9:0020d0
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug Uses which method: Get
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug Requires Auth: true
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug path is: /Streaming/channels/1/picture

ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:10:42 PM: debug The method is POST
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:10:42 PM: debug c0a801c9:0020d0
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:10:42 PM: debug Uses which method: Post
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:10:42 PM: debug Requires Auth: true
ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:10:42 PM: debug path is: /Streaming/channels/1/picture

I had to fix a bit of code for ports larger then 100

Also, remove the line above from your post:

ea0696fb-64c7-45b4-a8fe-045a98fe5069 12:09:55 PM: debug The Header is
[HOST:192.168.1.201:8400, Authorization:Basic THIS IS YOUR USERNAME AND PASSWORD]

Try the latest code

https://github.com/pstuart/smartthings/blob/master/generic_camera.groovy

Still not working for me :frowning:

This line of code is failing for me.

private String convertHexToIP(hex) {
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
} 

Getting this from the logs

java.lang.StringIndexOutOfBoundsException: String index out of range: 8 @ line 204

Appreciate all the work man!!!

What is the IP @tslagle13 you are entering in preferences?

same as above:

10.0.0.22

EDIT.

My camera is located at 10.0.0.22:8006

that way you have the full address if needed.

But how do you access them from within the app?