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

I may be onto the TrendNET/Hikvision Size issue. I have found a low res snapshot URL. If you have a second and want to take a look at it. Im available anytime if you are. Below is the Live Logging IDE not sure what im looking for here. GET/POST produce the same result.

3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug GET /live-video-frame.dyn?u=&p=************&device={localhost:60554-Media%20Source%5C001}&ts=1452306387476 HTTP/1.1
Accept: /
User-Agent: Linux UPnP/1.0 SmartThings
HOST: 192.168.168.98:8181

3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug The method is GET
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug The Header is [HOST:192.168.168.98:8181]
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug Uses which method: GET
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug Requires Auth: false
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug path is: /live-video-frame.dyn?u=&p=************&device={localhost:60554-Media%20Source%5C001}&ts=1452306387476
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug The device id configured is: C0A8A862:1FF5
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug 1ff5
3e151316-3a1c-4777-8a28-4cb3fd120615 8:33:55 PM: debug IP address entered is 192.168.168.98 and the converted hex code is c0a8a862

got my TrendNet TV-IP310PI/Hikvision DS-2CD2032-I working.

preferences {
	input("username",	"text",		title: "Camera username",	description: "Camera username for camera.", autoCorrect:false)
	input("password",	"password",	title: "Camera password",	description: "Camera password for camera.", autoCorrect:false)
	input("ip",			"text",		title: "IP or Hostname",	description: "WAN IP address or Hostname (minus - http://)", autoCorrect:false)
	input("port",		"number",	title: "Port",				description: "WAN Port number")}   

metadata {
definition (name: "WAN IP Camera", namespace: "WAN IP Camera", author: "Drew") {
	capability "Image Capture"
    capability "Actuator"
	capability "Switch"
}

tiles {

	standardTile("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: true) {
	  state "default", label: 'Take', action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
	}

	standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, canChangeBackground: false, decoration: "flat") {
		state "take", label: "Take Photo", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
		state "taking", label:'Taking', action: "", icon: "st.camera.take-photo", backgroundColor: "#00ff00"
		state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
    }
    /*standardTile("motion", "device.motionStatus", inactiveLabel: false, decoration: "flat") {
    	state "off", label: "off", action: "toggleMotion", icon: "st.motion.motion.inactive", backgroundColor: "#FFFFFF"
		state "on", label: "on", action: "toggleMotion", icon: "st.motion.motion.active",  backgroundColor: "#00ff00"
    }
    standardTile("refresh", "device.cameraStatus", inactiveLabel: false, decoration: "flat") {
        state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
    }*/
    carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }
    
	main "camera"
	details([
    "take", 
    //"motion", 
    //"refresh", 
    "cameraDetails"])    
}
}
def parse(String description) {
log.debug "Parsing '${description}'"
}

def parseCameraResponse(def response) {
log.debug( "parseCameraResponse() started" );

if(response.headers.'Content-Type'.contains("image/jpeg")) {
	def imageBytes = response.data

	if(imageBytes) {
		storeImage(getPictureName(), imageBytes)
	}
} else {
	log.error("${device.label} could not capture an image.")
}
}

def getPictureName() {
log.debug( "getPictureName() started" );

def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
"image" + "_$pictureUuid" + ".jpg"
}

def take() {
log.debug( "take() started" );

log.debug("${device.label} taking photo")

def strUrl = "http://${username}:${password}@${ip}:${port}/Streaming/channels/1/picture";
log.debug( "strUrl = ${strUrl}" );

httpGet( strUrl ){
	response -> log.info("${device.label} image captured")
	parseCameraResponse(response)
}
}
/*
def motion() {
log.debug( "motion() state" );

def strUrl = "http://${username}:${password}@${ip}:${port}/MotionDetection/1{}";
log.debug( "strUrl = ${strUrl}" );

httpPost( strUrl ){

}
}

def motionDisable = new XmlSlurper().parseText('''
<MotionDetection xmlns="http://www.w3.org/1999/xhtml/" version="1.0">
<id>1</id>
<enabled>false</enabled>
</MotionDetection>
   ''')
def motionEnable = new XmlSlurper().parseText('''
<MotionDetection xmlns="http://www.w3.org/1999/xhtml/" version="1.0">
<id>1</id>
<enabled>True</enabled>
</MotionDetection>
   ''')

Because the interface gives lots of detail (about the defined regions), you will probably find it easiest to do what I described above.  Get the existing settings like this:
Code: [Select]
curl http://user:password@ipaddress/MotionDetection/1 >resp.xml

Then simply edit the resp.xml file downloaded by that command (and save it as, for example: motionoff.xml) and change  the top <enabled>true</enabled> to <enabled>false</enabled>, then send it back up again with:
Code: [Select]
curl -T motionoff.xml  http://user:password@ipaddress/MotionDetection/1

To turn it back on, send a version of the file with enabled set to true.  Just remember to update your    scripts if you change the region on the camera (because the XML also contains the region definitions).
*/

This works great thanks,
Using it to connect to my Hikvision NVR, url is a little different -
http://${user}:${password}@${ip}/ISAPI/Streaming/channels/${cam}01/picture
cam variable is the number camera connected to nvr.
Any idea how to get WAN ip from smartthings? As it my provider keeps changing it.
Thanks

setup DDNS on the NVR or Camera’/s. then use the host name you setup.

I have hikonline DDNS setup but that hostname doesnt work. Maybe i need a different ddns provider. Have you tried one that works? Cheers

Tried a different DDNS provider and it worked, FYI I’m using an asus DDNS now but i was using hikvisions own hiddns and it didnt work.
Cheers,
Simple yet powerful.

1 Like

Did you resolve the connection to the QSee NVR ? I am still struggling with it. I am finally able to connect using the following setting, but the image is not coming up fully. It just shows the top of image and the rest scrambled. Looks like some resolution issue.
Camera path: /cgi-bin/snapshot.cgi?channel=1
POST

No. I never was able to get it to work consistently. I don’t even have the QSee NVR at this point.

Hi,

I am trying the following code to control my Foscam IP Camera.

/**
 *  Foscam
 *
 *  Author: danny@smartthings.com
 *  Author: brian@bevey.org
 *  Date: 5/2/14
 *
 *  Modified example Foscam device type to support dynamic input of credentials
 *  and enable / disable motion alarm to easily integrate into homemade
 *  security systems (when away, mark "alarmStatus" as "on", when present, mark
 *  "alarmStatus" as "off".  For use with email or FTP image uploading built
 *  into Foscam cameras.
 *
 *  Capability: Image Capture, Polling
 *  Custom Attributes: setStatus, alarmStatus
 *  Custom Commands: alarmOn, alarmOff, toggleAlarm, left, right, up, down,
 *                   pause, set, preset, preset1, preset2, preset3
 */

preferences {
  input("username", "text",     title: "Username",   description: "Your Foscam username")
  input("password", "password", title: "Password",   description: "Your Foscam password")
  input("ip",       "text",     title: "IP address", description: "The IP address of your Foscam")
  input("port",     "text",     title: "Port",       description: "The port of your Foscam")
}

metadata {
  definition (name: "Foscam") {
    capability "Polling"
    capability "Image Capture"

    attribute "setStatus",  "string"
    attribute "alarmStats", "string"

    command "alarmOn"
    command "alarmOff"
    command "toggleAlarm"
    command "left"
    command "right"
    command "up"
    command "down"
    command "pause"
    command "set"
    command "preset"
    command "preset1"
    command "preset2"
    command "preset3"
  }

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

    standardTile("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
      state "default", label: "", action: "Image Capture.take", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF"
    }

    standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false, decoration: "flat") {
      state "take", label: "", action: "Image Capture.take", icon: "st.secondary.take", nextState:"taking"
    }

    standardTile("up", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "up", action: "up", icon: ""
    }

    standardTile("alarmStatus", "device.alarmStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
      state "off", label: "off", action: "toggleAlarm", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF"
      state "on", label: "on", action: "toggleAlarm", icon: "st.camera.dropcam-centered",  backgroundColor: "#53A7C0"
    }

    standardTile("left", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "left", action: "left", icon: ""
    }

    standardTile("pause", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "pause", label: "pause", action: "pause", icon: ""
    }

    standardTile("right", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "right", action: "right", icon: ""
    }

    standardTile("blank", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "pause", label: "", action: "pause", icon: ""
    }

    standardTile("down", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
      state "down", label: "down", action: "down", icon: ""
    }

    standardTile("set", "device.setStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
      state "set", label: "set", action: "set", icon: "",  backgroundColor: "#FFFFFF"
      state "setting", label: "set mode", action: "set", icon: "", backgroundColor: "#53A7C0"
    }

    standardTile("preset1", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
      state "preset1", label: "preset 1", action: "preset1", icon: ""
    }

    standardTile("preset2", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
      state "preset2", label: "preset 2", action: "preset2", icon: ""
    }

    standardTile("preset3", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
      state "preset3", label: "preset 3", action: "preset3", icon: ""
    }

    standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") {
      state "default", action:"polling.poll", icon:"st.secondary.refresh"
    }

    main "alarmStatus"
      details(["cameraDetails", "take", "up", "alarmStatus", "left", "pause", "right", "blank", "down", "set", "preset1", "preset2", "preset3", "refresh"])
  }
}

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

private take() {
  log.debug("Take a photo")

  api("snapshot", "") {
    log.debug("Image captured")

    if(it.headers.'Content-Type'.contains("image/jpeg")) {
      if(it.data) {
        storeImage(getPictureName(), it.data)
      }
    }
  }
}

def toggleAlarm() {
  if(device.currentValue("alarmStatus") == "on") {
    alarmOff()
  }

  else {
    alarmOn()
  }
}

private alarmOn() {
  api("set_alarm", "motion_armed=1") {
    log.debug("Alarm changed to: on")
    sendEvent(name: "alarmStatus", value: "on");
  }
}

private alarmOff() {
  api("set_alarm", "motion_armed=0") {
    log.debug("Alarm changed to: off")
    sendEvent(name: "alarmStatus", value: "off");
  }
}

def left() {
  api("decoder_control", "command=6") {
    log.debug("Executing 'left'")
  }
}

def right() {
  api("decoder_control", "command=4") {
    log.debug("Executing 'right'")
  }
}

def up() {
  api("decoder_control", "command=0") {
    log.debug("Executing 'up'")
  }
}

def down() {
  api("decoder_control", "command=2") {
    log.debug("Executing 'down'")
  }
}

def pause() {
  api("decoder_control", "command=1") {}
}

def preset1() {
  preset(1)
}

def preset2() {
  preset(2)
}

def preset3() {
  preset(3)
}

//go to a preset location
def preset(def num) {
  if(num == null) return

  if(device.currentValue("setStatus") == "setting") {
    setPreset(num)
  }

  else {
    log.debug("Go To Preset Location")
    //1 is 31, 2 is 33, 3 is 35
    def cmd = 30 + (num * 2) - 1

    api("decoder_control", "command=${cmd}") {}
  }
}

//set the preset number to the current location
def setPreset(def num) {
  log.debug("Set Preset")
  //1 is 30, 2 is 32, 3 is 34... 8 is 44
  int cmd = 28 + (num * 2)
  sendCmd(cmd)

  log.debug("Exit Set Mode")
  sendEvent(name: "setStatus", value: "set");
}

//toggle the the mode to set the preset
def set() {
  if(device.currentValue("setStatus") == "set") {
    log.debug("Entering Set Mode")
    sendEvent(name: "setStatus", value: "setting");
  }

  else {
    log.debug("Exit Set Mode")
    sendEvent(name: "setStatus", value: "set");
  }
}

def api(method, args = [], success = {}) {
  def methods = [
    "decoder_control": [uri: "http://${ip}:${port}/decoder_control.cgi${login()}&${args}", type: "post"],
    "snapshot":        [uri: "http://${ip}:${port}/snapshot.cgi${login()}&${args}",        type: "post"],
    "set_alarm":       [uri: "http://${ip}:${port}/set_alarm.cgi${login()}&${args}",       type: "post"],
    "reboot":          [uri: "http://${ip}:${port}/reboot.cgi${login()}&${args}",          type: "post"],
    "camera_control":  [uri: "http://${ip}:${port}/camera_control.cgi${login()}&${args}",  type: "post"],
    "get_params":      [uri: "http://${ip}:${port}/get_params.cgi${login()}",              type: "get"],
    "videostream":     [uri: "http://${ip}:${port}/videostream.cgi${login()}",             type: "get"]
  ]

  def request = methods.getAt(method)

  doRequest(request.uri, request.type, success)
}

private doRequest(uri, type, success) {
  log.debug(uri)

  if(type == "post") {
    httpPost(uri , "", success)
  }

  else if(type == "get") {
    httpGet(uri, success)
  }
}

private login() {
  return "?user=${username}&pwd=${password}"
}

def poll() {
  api("get_params", []) {
    it.data.eachLine {
      if(it.startsWith("var alarm_motion_armed=0")) {
        log.info("Polled: Alarm off")
        sendEvent(name: "alarmStatus", value: "off");
      }

      if(it.startsWith("var alarm_motion_armed=1")) {
        log.info("Polled: Alarm on")
        sendEvent(name: "alarmStatus", value: "on");
      }
    }
  }
}

But I get the following output as log:

646bc7da-2afe-4585-a778-9982ba15a456 14:49:26: error org.apache.http.conn.ConnectTimeoutException: Connect to 192.168.254.23:80 [/192.168.254.23] failed: connect timed out @ line 259
646bc7da-2afe-4585-a778-9982ba15a456 14:49:16: debug http://192.168.254.23:80/snapshot.cgi?user=admin&pwd=xxx123xxx&
646bc7da-2afe-4585-a778-9982ba15a456 14:49:16: debug Take a photo

What may be wrong ?

Thanks

First, ensure you can reach that IP. From a command line on a computer on your LAN, type:
ping 192.168.254.23
If it responds with a time, great. If it can’t reach it, you should fix that problem.

Next, if you curl the query, do you get a response?

i.e. from your command line (if you have Windows, you might have to install a curl first), you can issue
curl “http://192.168.254.23:80/snapshot.cgi?user=admin&pwd=xxx123xxx&” > test.jpg

This should result in a file, test.jpg, that is an image. If not, the problem is with the command to the camera. If it does work, the next question is

thanks for your reply.

I can reach the IP (ping)
and I can get the picture with that URL from a browser. I didn’t try with curl but web browser result is fine.

what is the next question ?

How large is the image? If you configure the camera to send a much smaller one, at least for testing, that may improve your odds.

Camera doesn’t have such feature to make jpeg image smaller.
And I’ve purchased RBoy’s foscam device handler. It works with no problems.
So it shouldn’t be about image size.

Arron, so you got your Hikvisions to work via an Asus DDNS? I’ve got 6 Hikvisions outside of my house and just now trying to get them setup with the NVR software. Mind walking me through how you got them working with Smartthings? Thanks!

Yes sure, What NVR is it? And do you have DDNS setup? Before you add SmartThings device better off making sure network is ready to go.

Arron,

It’s the iVMS-4200 software. Like I said still trying to figure it out. I have all 6 cameras in there but trying to get recorder setup etc. The program can be a bit confusing. Can access all six of them anywhere locally on PC’s and iPhones on their app, but haven’t opened up port forwarding to individual cameras or the NVR. And I have no DDNS set up right now. What’s the best way to move forward on this stuff?

Ok, My NVR is a physical hikvision device - http://goo.gl/yj4aMq.
With 6 separate cameras this becomes a large enough task to forward each camera.
I think you can forward ports from 4200 software so you can then just view nvr software outside of lan.
But no solution here - http://goo.gl/kLkbDE
I have no clue how to set that up. Also i know the url to get a static image from my nvr. You would have to google how to get static image from your nvr software.
The smartthings setup is easy but you you need to get your cameras accessible from outside lan to get it working.
You should in theory be able to get it working using local ip but i couldn’t seem to get it working.
I just stuck with what @drewross had done.

I have this working fine, wondering though as I tried to use the Photoburst app to take a picture when a sensor is open, the camera shows in the app as selectable but doesnt actually take the picture when contact opens.

Any ideas?

thanks

Hi All. Experiencing a wee bit of a problem with the code @pstuart so kindly wrote.

Lets start with what works. In a browser this is 100% OK:
http://192.168.0.11:3370/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=xXxXxXxX&pwd=xXxXxXxX
This is a Foscam C1 camera. An image is returned in the browser.

Installed the ST app & configuring it as follows:
Camera IP Address: 192.168.0.11
Camera Port: 3370
Camera Path to Image: /cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=xXxXxXxX&pwd=xXxXxXxX
Require User Auth: NO / OFF
POST or GET : GET
Camera User / Password : Both are blank

When pressing the TAKE button, this lovely ERROR shows up in the log:

8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug The method is GET
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug The Header is [HOST:192.168.0.10:3369]
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: error grails.validation.ValidationException: Validation Error(s) occurred during save():

  • Field error in object ‘physicalgraph.device.Device’ on field ‘deviceNetworkId’: rejected value [C0A8000A:0D29]; codes [physicalgraph.device.Device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique.error,device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.error.deviceNetworkId,device.deviceNetworkId.unique.error.java.lang.String,device.deviceNetworkId.unique.error,physicalgraph.device.Device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique,device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.deviceNetworkId,device.deviceNetworkId.unique.java.lang.String,device.deviceNetworkId.unique,unique.physicalgraph.device.Device.deviceNetworkId,unique.deviceNetworkId,unique.java.lang.String,unique]; arguments [deviceNetworkId,class physicalgraph.device.Device,C0A8000A:0D29]; default message [{0} must be unique]
    8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug GET /cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=xXxXxXxX&pwd=xXxXxXxX HTTP/1.1
    Accept: /
    User-Agent: Linux UPnP/1.0 SmartThings
    HOST: 192.168.0.10:3369

8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug Uses which method: GET
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug path is: /cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=xXxXxXxX&pwd=xXxXxXxX
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug Requires Auth: false
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug 0d29
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug The device id configured is: C0A8000A:0D29
8b2965b7-3529-472e-8201-46b7fdf8a618 9:55:36 AM: debug IP address entered is 192.168.0.10 and the converted hex code is c0a8000a

Any thoughts as to what is causing the error? Note that the error also occurs if I use a POST method.

Thanks in advance
J

reading the error it appears your DNI for your device is not unique, do you have another device using the same DNI?