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

You should be able to keep both device types, because it’s an IP based device you are not tying it directly to anything paired directly to the hub. You could have 10 different ‘devices’ connected to that same camera if you wanted.

The one thing you’ll need to check is if my code (which is based on Patrick’s) uses the correct URI commands for your camera. The DCS-942L uses “/images/jpeg.cgi” to retrieve a still image and “/config/motion.cgi” to set the motion detection variables.

Thanks again, will look into it.

Hmmm, the picture take function is not working, now. It did work earlier when I first tried it.

I tried both Patricks and your device types.

From the log, I see the take routine being called and then an instance of HubAction. But I’m not seeing parse being called. According to the API, it says we have to ensure that HubAction has to return a valid instance to continue the callback of parse. I’m not sure how to verify that as that seems to be executed by the cloud or the hub. I haven’t changed anything except by doing this at home versus work earlier.

take routine is working again. I have not changed anything and parse is now being called so I am seeing the picture. Not sure what happened.

However, after much searching on the internet, I’ve determined that the DCS-932L does not support config motion changes. It reports the motion settings, but I cannot configure the motion values over http or with GET/POST.

Seems to be that the DCS-942L is the cheapest DLINK camera to offer this support.

Thanks everyone.

I’ll take a look tomorrow, I have all the API documents for the d-link cameras, I’ll see what I can dig up for the DCS-932L. I know the 942L has the PIR which others don’t but I’m pretty sure they all have standard motion detection, though it might use a different command.

Also, sometime smartthings will just decide it doesn’t want to return the image, really messes with you while you’re trying to figure out new code.

Hey @Tuan_Sy_Tran,

Try this code, I modified my DCS-942L devicetype to (hopefully) work with the DCS-932L:

/**
 *	D-Link DCS-932L v1.0.0
 *  Modified from Generic Camera Device v1.0.07102014
 *
 *  Copyright 2014 patrick@patrickstuart.com
 *  Modified 2015 blebson
 *
 *  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: "DCS-932L", author: "blebson") {
		capability "Image Capture"
		capability "Sensor"
		capability "Switch"
        capability "Switch Level"
        capability "Refresh"
        
		attribute "hubactionMode", "string"
    
        
        
        
        
        command "motionOn"
        command "motionOff"        
        
	}

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

        standardTile("refresh", "command.refresh", inactiveLabel: false) {
        	state "default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon"        
    	}
        standardTile("motion", "device.switch", width: 1, height: 1, canChangeIcon: false) {
			state "off", label: 'Motion Off', action: "switch.on", icon: "st.motion.motion.inactive", backgroundColor: "#ccffcc", nextState: "toggle"
            state "toggle", label:'toggle', action: "", icon: "st.motion.motion.inactive", backgroundColor: "#53a7c0"
			state "on", label: 'Motion On', action: "switch.off", icon: "st.motion.motion.active", backgroundColor: "#EE0000", nextState: "toggle"            
		}         
       controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
            state "level", action:"switch level.setLevel"
        }
        
        main "motion"
        details(["cameraDetails", "take", "motion", "PIR", "refresh", "levelSliderControl"])
    }
}

def parse(String description) {
    log.debug "Parsing '${description}'"
    def map = [:]
	def retResult = []
	def descMap = parseDescriptionAsMap(description)
    def msg = parseLanMessage(description)
    //log.debug "status ${msg.status}"
    //log.debug "data ${msg.data}"
    
	//Image
	if (descMap["bucket"] && descMap["key"]) {
		putImageInS3(descMap)
	}      
    else if (descMap["headers"] && descMap["body"]){
    	def body = new String(descMap["body"].decodeBase64())
        log.debug "Body: ${body}"
    }
    
    if (msg.body) {
    
    //log.debug "Motion Enabled: ${msg.body.contains("enable=yes")}"
    //log.debug "Motion Disabled: ${msg.body.contains("enable=no")}"
    //log.debug "PIR Enabled: ${msg.body.contains("pir=yes")}"
    //log.debug "PIR Disabled: ${msg.body.contains("pir=no")}"
    
        if (msg.body.contains("MotionDetectionEnable=1")) {
            log.debug "Motion is on"
            sendEvent(name: "switch", value: "on");
        }
        else if (msg.body.contains("MotionDetectionEnable=0")) {
            log.debug "Motion is off"
            sendEvent(name: "switch", value: "off");
        }        
        if(msg.body.contains("MotionDetectionSensitivity="))
        {
        	//log.debug msg.body        
        	String[] lines = msg.body.split( '\n' )
        	//log.debug lines[2]
            String[] sensitivity = lines[2].split( '=' )
            //log.debug sensitivity[1]
            int[] senseValue = sensitivity[1].toInteger()
            //log.debug senseValue
            
            sendEvent(name: "level",  value: "${senseValue[0]}")
            //sendEvent(name: "switch.setLevel", value: "${senseValue}")
        }        
    }
}

// handle commands
def take() {
	def userpassascii = "${CameraUser}:${CameraPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host)
    def porthex = convertPortToHex(CameraPort)
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = "/image/jpeg.cgi" 
    log.debug "path is: $path"
    
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
   	headers.put("Authorization", userpass)
    
    log.debug "The Header is $headers"
    
    def method = "GET"    
    
    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 motionCmd(int motion, String attr)
{
	def userpassascii = "${CameraUser}:${CameraPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host)
    def porthex = convertPortToHex(CameraPort)
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
    
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
    headers.put("Authorization", userpass)
    
    log.debug "The Header is $headers"
    
    if (motion == 1){
 def path = "/motion.cgi?$MotionDetectionEnable=1&ConfigReboot=No"
 log.debug "path is: $path"
  try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
    	path: path,
    	headers: headers
        )
        	
   
    log.debug hubAction
    return hubAction
    
    }
    catch (Exception e) {
    	log.debug "Hit Exception $e on $hubAction"
    }
  }
  else
  {
  def path = "/motion.cgi?$MotionDetectionEnable=0&ConfigReboot=No"
 log.debug "path is: $path"  
  try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
    	path: path,
    	headers: headers        
        )
     
    log.debug hubAction
    return hubAction
    
    }
    catch (Exception e) {
    	log.debug "Hit Exception $e on $hubAction"
    }
  
 
}
}
def sensitivityCmd(int percent)
{
	def userpassascii = "${CameraUser}:${CameraPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host)
    def porthex = convertPortToHex(CameraPort)
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
       
    
    log.debug "Sensitivity is ${percent}"
    
    def path = "/motion.cgi?MotionDetectionSensitivity=${percent}&ConfigReboot=No"
    log.debug "path is: $path"
        
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
    headers.put("Authorization", userpass)
    
    log.debug "The Header is $headers"
   
  try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
    	path: path,
    	headers: headers
        )
        	
   
    log.debug hubAction
    return hubAction
    
    }
    catch (Exception e) {
    	log.debug "Hit Exception $e on $hubAction"
    }
  
}

def putImageInS3(map) {
	log.debug "firing s3"
    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() }
	}
}

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
}


def on() {
	log.debug "Enabling motion detection"
    return motionCmd(1)    
}

def off() {
	log.debug "Disabling motion detection"
    return motionCmd(0)    
}

def setLevel(percent) {
	log.debug "Executing 'setLevel'"
	return sensitivityCmd(percent)	   
}
def refresh(){

	log.debug "Refresh"
	def userpassascii = "${CameraUser}:${CameraPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host)
    def porthex = convertPortToHex(CameraPort)
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
    def path = "/motion.cgi"
    log.debug "path is: $path"
    
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
    headers.put("Authorization", userpass)
    
    log.debug "The Header is $headers"
   
  try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
    	path: path,
    	headers: headers
        )
        	
   
    log.debug hubAction
    return hubAction
    
    }
    catch (Exception e) {
    	log.debug "Hit Exception $e on $hubAction"
    }
  
  
}

Hey Ben,

I did modify your code to be similar to this, with updated motion.cgi path and nodes. I’ve read other places that the 932L simply doesn’t respond to these commands. It will only report the current settings. I’ve also tried testing with a browser:
http://ip_address/motion.cgi?MotionDetectionEnable=1&user=xxxx&password=xxxx

motion.cgi is definitley correct. Other paths will respond with an error.

Tried a combination of these commands. It doesn’t update.

I’ll probably upgrade to a 942l and return the 932l.

Thanks for your help, though.

Have you tried the code I just posted? I think the URL you need to send is: http://user:pass@IP:port/motion.cgi?MotionDetectionEnable=1&ConfigReboot=No

Also, if you’re going to return the camera I would just upgrade to one that is officially supported.

No, I did not actually… Hmm, I didn’t find information on the ConfigReboot parameter.

Unfortunately, I just recently returned the camera. :smile:

Thanks

Gotcha, well hopefully someone can take a look at the DeviceType and see if it works or not, I don’t have the camera to be able to test it.

Hi @David_Solis

I also have hikvision cameras and I am having the same issue. @pstuart Can you help us out? The request seems to fire off OK but I never get the S3 callback

Thanks !

Kristopher

Can you get an image in a browser? What type of authentication is this using? Can you turn it off on the camera and still get the image?

Yeah - It must be something weird with the hikvision header. I changed the URL to my iSpy (which is just a rebroadcast of the JPG) and it worked fine. Let me dig a little more since I think its on Hikvision’s side

(Separately, have you messed around with the VIDEO events much?)

By the way, are there any sweet use cases for cameras?

In ST? Hmmm… Not yet… Unless you use the supported video cameras and SHM, the recorded clips could be cool, if it worked.

Still waiting to see if they will release the video stuff to make other cameras work, its all closed source at the moment.

I bought a Samsung SmartCam to test out, it works pretty good, but was really surprised they didn’t include image capture in the device type.

It’s still in “beta” so hopefully we will see some cool use cases coming up.

Outside of ST, the coolest use I have is hooking up my doorbell press to snap a photo from my camera and email it to me so I can see who’s at the door.

Hi All can someone please help me locate this once I’ve edited it and saved/published it.

Thanks

Never mind, found it. However, everything I try to configure it I get red “Unexpected error occurred”.

Also “Device Network ID” - What is that meant to be?

Sorry me again I know -
Just went through the debugging at the bottom, got this:

5602650a-35af-4bb2-8a15-36dd98fc37bc 21:57:33: error java.lang.NullPointerException: Cannot invoke method tokenize() on null object @ line 178

Means nothing to me?

Here is something I just hacked together

/**
 *  Motion Capture
 *
 *  Copyright 2015 Kristopher Kubicki
 *
 */
definition(
    name: "Motion Capture",
    namespace: "KristopherKubicki",
    author: "kristopher@acm.org",
    description: "Captures an image when motion is detected",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/intruder_motion-cam.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/intruder_motion-cam@2x.png")


preferences {
    section("Activate these cameras...") {
        input "cameras", "capability.imageCapture", title:"Which cameras", multiple:true, required:true
    }
    section("With these motion sensors") {
		input "motions", "capability.motionSensor", title: "Which motions?", multiple:false, required: true
    }
}

def installed() {
	unsubscribe()
    unschedule()
    initialize()
}

def updated() {
	unsubscribe()
    unschedule()
    initialize()
}

private def initialize() {
	log.debug("initialize() with settings: ${settings}")
	subscribe(motions, "motion.active", motionHandler)
}

def motionHandler(evt) { 
	cameras?.take()
}

There used to be a photoburst app in the labs, I think. I use it with a couple of cameras for motion and contact sensors. It would allow capture on various different events.