D-Link IP Camera device type (DCS-930L tested)

I own a couple of D-Link IP cameras, mostly to keep an eye on the dog while we’re out, and I spent some time last weekend hacking around with some of the existing IP camera implementations. The credit here really goes to Danny and Brian, who worked on various other Device Types and got 99% of this working.

It’s pretty lightweight; basically you can grab a still by tapping on the icon in the ‘Things’ list or opening the device and tapping ‘take photo’. It’s supported as an image capture device, so you can use SmartApps like “take photo burst” to fire off a round of photos when some action takes place.

I’ve hosted the device type on GitHub for now, although I’m not sure if it should be shared some other way. Please feel free to fork or otherwise contribute to this – I’m using this as a learning experience!

###Known Issues

Currently requires an accessible external IP or domain name. I’m looking into what it would take for local LAN access to cameras without this step!

11 Likes

Thanks for this… but as a newbie I have to admit I am struggling with some of the discovery in SmartThings; especially device types.

So I have created a new device type, pasted in this code, and published it. Now I assume I create a new device; the device-type “is” in the popup menu. I assume I set the capability to image capture; but what else needs to be filled out.

No matter what I try I am never prompted for the input strings (IP, user, pswd, etc), and of course the installed device does not work. I’d very much appreciate some help on this.

Thanks,

Scott in Pollock

When adding the new device from the API website, you need to set a garbage Device Network Id (mine are “C1” and “C2”). You shouldn’t have to set any capabilities, certainly not from the ‘add new device’ steps.

Once you’ve added the network ID, you need to associate it with your location/hub/etc. Once that’s done, you should be able to see the device in your mobile app and tap the gear to access the configuration. From there, you’d add the device settings like IP and password.

Hopefully that helps. I’m not sure if there’s better documentation on adding devices and types, but I’ll see if I can track something down.

2 Likes

Thanks Eric…

Unfortunately all I get is…

bb2463ab-7498-43c6-83ac-b2ae1958c5d8 1:28:52 PM: error org.apache.http.conn.ConnectTimeoutException: Connect to 10.0.0.26:6701 timed out @ line 58
bb2463ab-7498-43c6-83ac-b2ae1958c5d8 1:28:42 PM: info null taking photo
bb2463ab-7498-43c6-83ac-b2ae1958c5d8 1:25:26 PM: error org.apache.http.conn.ConnectTimeoutException: Connect to 10.0.0.26:6701 timed out @ line 58
bb2463ab-7498-43c6-83ac-b2ae1958c5d8 1:25:16 PM: info null taking photo

I can confirm the URL at http://10.0.0.26:6701/image/jpeg.cgi is working just fine from the browser.

Perhaps my setup for the device-type is missing something…

Ah! Well, local IPs don’t seem to work in current ST versions - you need to use port forwarding and a hostname, if the camera doesn’t have a dedicated IP.

I use no-ip to run a small rpi server at home, and I just added on port forwarding rules for my D-Link cameras. I logged into them, changed the port from 80 to whatever, and pass that port straight through my router.

Pain in the butt, but it seems like what we have to do right now?

@egid

Hey again. I am trying to take a whack at getting this working locally, but I still need to get my head around some things. So, if you don’t mind:

1.) Got this working by forwarding a port and coming in from the WAN side… but what’s the point? The images are not saved locally (looks like they’re saved in some temp directory on Amazon’s servers), and when you close the device all references to them seems to be gone.

2.) You mention a “take photo burst” SmartApp (maybe that is the missing piece of the puzzle), where would I find that?

Thanks!

Scottin Pollock

  1. I’m not sure if local LAN connections are supported - I do intend to dig into that more, but haven’t gotten to it. This is mostly a modification of an existing Foscam device type, which I couldn’t get working with local IPs. I agree that it’s pretty silly to have to open a port when the camera is on the same network as the hub.

  2. It’s still in Labs - here’s how to get to it on iOS (can’t help you on Android, I’m afraid):

    • use the add button to bring up SmartSetup
    • scroll the top section until you see “More”
    • tap “SmartThingsLabs” and scroll a bit
    • the app is described as “Take a burst of photos and send a push notification when various events happen around your home.” and called “Photo Burst When”.

Hopefully that gets you what you need!

Bummer. So I guess the source is not available to look at?

Have you used this? Where are the images saved to?

They just show up in the top gallery in your camera device. There’s nothing crazy going on, and they (unfortunately) don’t show up in your push notifications or anything.

As far as source, it’s available if you create a new app, then click “Browse SmartApps…” in the top right. It’s under “Photo Burst When” there and you should be able to replace your blank app with the code and start hacking.

I don’t why I’m not getting this. Installed the device as is, and “Photo Burst When”, but when it triggers (and I get a notification), I see no images anywhere.

Where should they be? Please explain it to me as if I am stupid (which I obviously am).

I have this mostly working. But I can’t see the photos.

Go to Things, find your camera device, and tap the small gear. This gets you into the control panel, which is where the photos should show up.

Unless you have photo burst set up, or tap “take photo”, you won’t have anything up there.

Forgive me if you’re already doing this. If it’s not working after those steps, I have no clue!

Got it working by making sure to have it being access from an external IP.

Can you access this from the mydlink site? Seems a bit excessive to have to route out and back in. Is this a limitation of the Hub that can be corrected in the future or will it always be like this?

I’d love to, but as far as I can tell there’s no public API for manipulating mydlink cameras and fetching snapshots.

You’re right that this is a bit excessive. I’m under the impression that work is going on (or is maybe done, but I haven’t found the documentation) to enable link-local addressing from the existing hub. Once I get some free time to do more work, I’ll try to get that working and update the device type accordingly.

Until then, this saves me from having to log into the mydlink app every time I want to take a peek at how the dog is doing.

I also have this working now with this camera, Hikvision ds-2cd2432f-iw, with a slight code mod and it’s working great. Super camera BTW.

Not sure if you have implemented this, but have you thought of making your cameras take pics when there is motion? I wrote devicetypes for my 932s and 5020, and when I leave home the “motion sensor” is armed… so it takes pics when I am not home and it senses motion.

I see this… thanks. But what I am trying to determine is if they can be stored anywhere (cloud, on the device)?

posted my device types for my 932L and 5020L. The 5020L I can move, and both of them I can turn on and off the motion sensing. I have them setup as switches, so when I leave I can turn them “on” which turns on the motion sensing to capture pics if they sense motion. When I come home, they turn “off” which turns off the motion sensing.


2 Likes

And it is not necessary. Here is a new ‘local’ device type. It’s only requirement is to input the IP:PORT into Device Network Id in the device, as hex.

/**
 *  D-Link Camera
 *
 *  Copyright 2014 Scottin Pollock
 *
 *  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: "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: "Image Capture.take", icon: "st.camera.dropcam-centered", 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.dropcam-centered", backgroundColor: "#FFFFFF", nextState:"taking"
		}

		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
}
1 Like