Foscam Universal Internal Device Type (HD\Non-HD with Alarm\LED\PTZ Control)

I mostly did a rewrite of my Foscam HD device type. This device type should support all Foscam cameras using an internal IP address, HD and non-HD versions. The alarm and IR lights can be controlled. PTZ functionality is also included. This will still work with SmartApps that I created for Foscam cameras.

Thanks to posts by @scottinpollock and @pstuart I was able to get this done.

Notes & Issues:

  • Polling for the non-HD cameras is currently not working. It looks like this an issue with either the system not being able to handle the large amount of data that is returned or there is something in the data that is causing a problem. I have contacted support regarding this so hopefully we’ll be able to figure it out.
  • Non-HD cameras only support on and off for LED controls.
  • Foscam does not support retrieving settings for manual LED modes for HD cameras and no support for non-HD cameras for polling LED status. When refreshing an HD camera that is in manual mode the On and Off buttons will turn green to show it is in manual mode.
  • I’m not sure if this is a bug in the phone app but sometimes when changing multiple preferences at the same time only one will save.
  • HD cameras use custom named presets and cruise maps and the API refers directly to these names. These names will need to be entered in the preferences.
  • Some complex passwords are not supported. I think this might be because everything is passed through a URL. (Thanks @pitchdarkice for figuring this out)

Compatible SmartApps I made for this (shared in the IDE):

  • Foscam Mode Alarm
  • Foscam Presence Alarm
  • Foscam Turn Off LEDs with Mode Change

Updates:

  • Added PTZ controls. Check the Preferences screen for additional options for PTZ.
  • Minor change to the way commands were sent to HD cameras.
  • Fixed Boolean comparisons in the code. Device is working again. (Thanks @thrash99er)

https://github.com/skp19/st_foscam_universal


If you find this app useful, please support the developer:

21 Likes

Awesome work man!!!

Hope you don’t mind but I impoved a little on your device type. I added PTZ and Preset controls.

Here’s the code.

Regular Foscam

/**
 *  Foscam Universal Device
 *
 *  Copyright 2014 skp19
 *
 */
metadata {
	definition (name: "Foscam Universal Device", namespace: "skp19", author: "skp19") {
		capability "Polling"
		capability "Image Capture"
        
        attribute "alarmStatus", "string"
        attribute "ledStatus",   "string"
        attribute "hubactionMode", "string"
    
		command "alarmOn"
		command "alarmOff"
		command "toggleAlarm"
		command "toggleLED"
		command "ledOn"
		command "ledOff"
		command "ledAuto"
        command "preset1"
        command "preset2"
        command "preset3"
        command "set1"
        command "set2"
        command "set3"
        command "up"
        command "down"
        command "right"
        command "left"
        command "center"
        command "pasue"
        
	}
    
    preferences {
        input("ip", "string", title:"Camera IP Address", description: "Camera IP Address", required: true, displayDuringSetup: true)
        input("port", "string", title:"Camera Port", description: "Camera Port", defaultValue: 80 , required: true, displayDuringSetup: true)
        input("username", "string", title:"Camera Username", description: "Camera Username", required: true, displayDuringSetup: true)
        input("password", "password", title:"Camera Password", description: "Camera Password", required: true, displayDuringSetup: true)
        input("hdcamera", "bool", title:"HD Foscam Camera? (9xxx Series)", description: "Type of Foscam Camera", required: true, displayDuringSetup: true)
	}

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

        standardTile("camera", "device.alarmStatus", width: 1, height: 1, canChangeIcon: true, inactiveLabel: true, canChangeBackground: true) {
          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("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("alarmStatus", "device.alarmStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "off", label: "off", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-off", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-on",  backgroundColor: "#53A7C0"
        }
        
        standardTile("ledStatus", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "auto", label: "auto", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#53A7C0"
          state "off", label: "off", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "toggleLED", icon: "st.Lighting.light11", backgroundColor: "#FFFF00"
          state "manual", label: "manual", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFF00"
        }
        
		standardTile("led", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "auto", label: "auto", action: "ledOff", icon: "st.Lighting.light11", backgroundColor: "#53A7C0"
          state "off", label: "off", action: "ledAuto", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "ledon", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
        }
        
        standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") {
          state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
        }
        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("up", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "up", action: "up", icon: ""
    }
    standardTile("down", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "down", action: "down", icon: ""
    }
    standardTile("left", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "left", action: "left", 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 "take", label: "", action: "pause", icon: ""
    }
standardTile("set1", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 1", action: "set1", icon: ""
    }
    standardTile("set2", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 2", action: "set2", icon: ""
    }
    standardTile("set3", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 3", action: "set3", icon: ""
    }

        main "camera"
          details(["cameraDetails", "take", "up", "alarmStatus", "left", "blank", "right", "led", "down", "blank", "preset1", "preset2", "preset3", "set1", "set2", "set3","refresh"])
	}
}

def take() {
	log.debug("Taking Photo")
	sendEvent(name: "hubactionMode", value: "s3");
    if(hdcamera == "true") {
		hubGet("cmd=snapPicture2")
    }
    else {
    	hubGet("/snapshot.cgi?")
    }
}

def toggleAlarm() {
	log.debug "Toggling Alarm"
	if(device.currentValue("alarmStatus") == "on") {
    	alarmOff()
  	}
	else {
    	alarmOn()
	}
}

def alarmOn() {
	log.debug "Enabling Alarm"
    sendEvent(name: "alarmStatus", value: "on");
    if(hdcamera == "true") {
		hubGet("cmd=setMotionDetectConfig&isEnable=1")
    }
    else {
    	hubGet("/set_alarm.cgi?motion_armed=1&")
    }
}

def alarmOff() {
	log.debug "Disabling Alarm"
    sendEvent(name: "alarmStatus", value: "off");
    if(hdcamera == "true") {
		hubGet("cmd=setMotionDetectConfig&isEnable=0")
    }
    else {
    	hubGet("/set_alarm.cgi?motion_armed=0&")
    }
}

//Toggle LED's
def toggleLED() {
  log.debug("Toggle LED")

  if(device.currentValue("ledStatus") == "auto") {
    ledOn()
  }

  else if(device.currentValue("ledStatus") == "on") {
    ledOff()
  }
  
  else {
    ledAuto()
  }
}

def ledOff() {
    log.debug("LED changed to: off")
    sendEvent(name: "ledStatus", value: "off");
    if(hdcamera == "true") {
    	delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=closeInfraLed")])
    }
    else {
    	hubGet("/decoder_control.cgi?command=94&")
    }
}

def ledAuto() {
    log.debug("LED changed to: auto")
    sendEvent(name: "ledStatus", value: "auto");
	if(hdcamera == "true") {
		hubGet("cmd=setInfraLedConfig&mode=0")
    }
    else {
    	hubGet("/decoder_control.cgi?command=95&")
    }
}

def poll() {

	sendEvent(name: "hubactionMode", value: "local");
    //Poll Motion Alarm Status and IR LED Mode
    if(hdcamera == "true") {
		delayBetween([hubGet("cmd=getMotionDetectConfig"), hubGet("cmd=getInfraLedConfig")])
    }
    else {
    	hubGet("/get_params.cgi?")
    }
}

private getLogin() {
	if(hdcamera == "true") {
    	return "&usr=${username}&pwd=${password}"
    }
    else {
    	return "user=${username}&pwd=${password}"
    }
}

def preset1() {
    	hubGet("/decoder_control.cgi?command=31&")

}

def preset2() {

    	hubGet("/decoder_control.cgi?command=33&")
}

def preset3() {
    	hubGet("/decoder_control.cgi?command=35&")
}

def up() {
    	delayBetween([
		hubGet("/decoder_control.cgi?command=0&"),
		hubGet("/decoder_control.cgi?command=1&")
	], 500)
       
}

def down() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=2&"),
		hubGet("/decoder_control.cgi?command=3&")
	], 500)
}


def right() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=4&"),
		hubGet("/decoder_control.cgi?command=5&")
	], 500)
}


def left() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=6&"),
		hubGet("/decoder_control.cgi?command=7&")
	], 500)
}

def set1() {
    	hubGet("/decoder_control.cgi?command=30&")
}

def set2() {
    	hubGet("/decoder_control.cgi?command=32&")
}

def set3() {
    	hubGet("/decoder_control.cgi?command=34&")
}



private hubGet(def apiCommand) {
	//Setting Network Device Id
    def iphex = convertIPtoHex(ip)
    def porthex = convertPortToHex(port)
    device.deviceNetworkId = "$iphex:$porthex"
    log.debug "Device Network Id set to ${iphex}:${porthex}"

	log.debug("Executing hubaction on " + getHostAddress())
    def uri = ""
    if(hdcamera == "true") {
    	uri = "/cgi-bin/CGIProxy.fcgi?" + apiCommand + getLogin()
	}
    else {
    	uri = apiCommand + getLogin()
    }
    log.debug uri
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
        path: uri,
        headers: [HOST:getHostAddress()]
    )
    if(device.currentValue("hubactionMode") == "s3") {
        hubAction.options = [outputMsgToS3:true]
        sendEvent(name: "hubactionMode", value: "local");
    }
	hubAction
}

//Parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
    
    def map = [:]
    def retResult = []
    def descMap = parseDescriptionAsMap(description)
        
    //Image
	if (descMap["bucket"] && descMap["key"]) {
		putImageInS3(descMap)
	}

	//Status Polling
    else if (descMap["headers"] && descMap["body"]) {
        def body = new String(descMap["body"].decodeBase64())
        if(hdcamera == "true") {
            def langs = new XmlSlurper().parseText(body)

            def motionAlarm = "$langs.isEnable"
            def ledMode = "$langs.mode"

            //Get Motion Alarm Status
            if(motionAlarm == "0") {
                log.info("Polled: Alarm Off")
                sendEvent(name: "alarmStatus", value: "off");
            }
            else if(motionAlarm == "1") {
                log.info("Polled: Alarm On")
                sendEvent(name: "alarmStatus", value: "on");
            }

            //Get IR LED Mode
            if(ledMode == "0") {
                log.info("Polled: LED Mode Auto")
                sendEvent(name: "ledStatus", value: "auto")
            }
            else if(ledMode == "1") {
                log.info("Polled: LED Mode Manual")
                sendEvent(name: "ledStatus", value: "manual")
            }
    	}
        else {
        	if(body.find("alarm_motion_armed=0")) {
				log.info("Polled: Alarm Off")
                sendEvent(name: "alarmStatus", value: "off")
            }
        	else if(body.find("alarm_motion_armed=1")) {
				log.info("Polled: Alarm On")
                sendEvent(name: "alarmStatus", value: "on")
            }
            //The API does not provide a way to poll for LED status on 8xxx series at the moment
        }
	}
}

def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
}

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() }
	}
}

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

private getHostAddress() {
	return "${ip}:${port}"
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex

}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    return hexport
}

PTZ Flipped Device Type

/**
 *  Foscam Universal Device
 *
 *  Copyright 2014 skp19
 *
 */
metadata {
	definition (name: "Foscam Universal Device PTZ Flipped", namespace: "skp19", author: "skp19") {
		capability "Polling"
		capability "Image Capture"
        
        attribute "alarmStatus", "string"
        attribute "ledStatus",   "string"
        attribute "hubactionMode", "string"
    
		command "alarmOn"
		command "alarmOff"
		command "toggleAlarm"
		command "toggleLED"
		command "ledOn"
		command "ledOff"
		command "ledAuto"
        command "preset1"
        command "preset2"
        command "preset3"
        command "set1"
        command "set2"
        command "set3"
        command "up"
        command "down"
        command "right"
        command "left"
        command "center"
        command "pasue"
        
	}
    
    preferences {
        input("ip", "string", title:"Camera IP Address", description: "Camera IP Address", required: true, displayDuringSetup: true)
        input("port", "string", title:"Camera Port", description: "Camera Port", defaultValue: 80 , required: true, displayDuringSetup: true)
        input("username", "string", title:"Camera Username", description: "Camera Username", required: true, displayDuringSetup: true)
        input("password", "password", title:"Camera Password", description: "Camera Password", required: true, displayDuringSetup: true)
        input("hdcamera", "bool", title:"HD Foscam Camera? (9xxx Series)", description: "Type of Foscam Camera", required: true, displayDuringSetup: true)
	}

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

        standardTile("camera", "device.alarmStatus", width: 1, height: 1, canChangeIcon: true, inactiveLabel: true, canChangeBackground: true) {
          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("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("alarmStatus", "device.alarmStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "off", label: "off", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-off", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-on",  backgroundColor: "#53A7C0"
        }
        
        standardTile("ledStatus", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "auto", label: "auto", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#53A7C0"
          state "off", label: "off", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "toggleLED", icon: "st.Lighting.light11", backgroundColor: "#FFFF00"
          state "manual", label: "manual", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFF00"
        }
        
		standardTile("led", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
          state "auto", label: "auto", action: "ledOff", icon: "st.Lighting.light11", backgroundColor: "#53A7C0"
          state "off", label: "off", action: "ledAuto", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
          state "on", label: "on", action: "ledon", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF"
        }
        
        standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") {
          state "refresh", action:"polling.poll", icon:"st.secondary.refresh"
        }
        standardTile("preset1", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "Preset 1", label: "Preset 1", action: "preset1", icon: ""
    }
    standardTile("preset2", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "Preset 2", label: "Preset 2", action: "preset2", icon: ""
    }
    standardTile("preset3", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "Preset 3", label: "Preset 3", action: "preset3", icon: ""
    }
    standardTile("up", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "up", action: "up", icon: ""
    }
    standardTile("down", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "down", action: "down", icon: ""
    }
    standardTile("left", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "left", action: "left", 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 "take", label: "", action: "pause", icon: ""
    }
    standardTile("set1", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 1", action: "set1", icon: ""
    }
    standardTile("set2", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 2", action: "set2", icon: ""
    }
    standardTile("set3", "device.image", width: 1, height: 1, canChangeIcon: false,  canChangeBackground: false, decoration: "flat") {
      state "take", label: "set preset 3", action: "set3", icon: ""
    }

        main "camera"
          details(["cameraDetails", "take", "up", "alarmStatus", "left", "blank", "right", "led", "down", "blank", "preset1", "preset2", "preset3", "set1", "set2", "set3","refresh"])
	}
}

def take() {
	log.debug("Taking Photo")
	sendEvent(name: "hubactionMode", value: "s3");
    if(hdcamera == "true") {
		hubGet("cmd=snapPicture2")
    }
    else {
    	hubGet("/snapshot.cgi?")
    }
}

def toggleAlarm() {
	log.debug "Toggling Alarm"
	if(device.currentValue("alarmStatus") == "on") {
    	alarmOff()
  	}
	else {
    	alarmOn()
	}
}

def alarmOn() {
	log.debug "Enabling Alarm"
    sendEvent(name: "alarmStatus", value: "on");
    if(hdcamera == "true") {
		hubGet("cmd=setMotionDetectConfig&isEnable=1")
    }
    else {
    	hubGet("/set_alarm.cgi?motion_armed=1&")
    }
}

def alarmOff() {
	log.debug "Disabling Alarm"
    sendEvent(name: "alarmStatus", value: "off");
    if(hdcamera == "true") {
		hubGet("cmd=setMotionDetectConfig&isEnable=0")
    }
    else {
    	hubGet("/set_alarm.cgi?motion_armed=0&")
    }
}

//Toggle LED's
def toggleLED() {
  log.debug("Toggle LED")

  if(device.currentValue("ledStatus") == "auto") {
    ledOn()
  }

  else if(device.currentValue("ledStatus") == "on") {
    ledOff()
  }
  
  else {
    ledAuto()
  }
}

def ledOff() {
    log.debug("LED changed to: off")
    sendEvent(name: "ledStatus", value: "off");
    if(hdcamera == "true") {
    	delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=closeInfraLed")])
    }
    else {
    	hubGet("/decoder_control.cgi?command=94&")
    }
}

def ledAuto() {
    log.debug("LED changed to: auto")
    sendEvent(name: "ledStatus", value: "auto");
	if(hdcamera == "true") {
		hubGet("cmd=setInfraLedConfig&mode=0")
    }
    else {
    	hubGet("/decoder_control.cgi?command=95&")
    }
}

def poll() {

	sendEvent(name: "hubactionMode", value: "local");
    //Poll Motion Alarm Status and IR LED Mode
    if(hdcamera == "true") {
		delayBetween([hubGet("cmd=getMotionDetectConfig"), hubGet("cmd=getInfraLedConfig")])
    }
    else {
    	hubGet("/get_params.cgi?")
    }
}

private getLogin() {
	if(hdcamera == "true") {
    	return "&usr=${username}&pwd=${password}"
    }
    else {
    	return "user=${username}&pwd=${password}"
    }
}

def preset1() {
    	hubGet("/decoder_control.cgi?command=31&")

}

def preset2() {

    	hubGet("/decoder_control.cgi?command=33&")
}

def preset3() {
    	hubGet("/decoder_control.cgi?command=35&")
}

def up() {
    	delayBetween([
		hubGet("/decoder_control.cgi?command=2&"),
		hubGet("/decoder_control.cgi?command=3&")
	], 500)
       
}

def down() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=0&"),
		hubGet("/decoder_control.cgi?command=1&")
	], 500)
}


def right() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=6&"),
		hubGet("/decoder_control.cgi?command=7&")
	], 500)
}


def left() {
    	    	delayBetween([
		hubGet("/decoder_control.cgi?command=4&"),
		hubGet("/decoder_control.cgi?command=5&")
	], 500)
}

def set1() {
    	hubGet("/decoder_control.cgi?command=30&")
}

def set2() {
    	hubGet("/decoder_control.cgi?command=32&")
}

def set3() {
    	hubGet("/decoder_control.cgi?command=34&")
}



private hubGet(def apiCommand) {
	//Setting Network Device Id
    def iphex = convertIPtoHex(ip)
    def porthex = convertPortToHex(port)
    device.deviceNetworkId = "$iphex:$porthex"
    log.debug "Device Network Id set to ${iphex}:${porthex}"

	log.debug("Executing hubaction on " + getHostAddress())
    def uri = ""
    if(hdcamera == "true") {
    	uri = "/cgi-bin/CGIProxy.fcgi?" + apiCommand + getLogin()
	}
    else {
    	uri = apiCommand + getLogin()
    }
    log.debug uri
    def hubAction = new physicalgraph.device.HubAction(
    	method: "GET",
        path: uri,
        headers: [HOST:getHostAddress()]
    )
    if(device.currentValue("hubactionMode") == "s3") {
        hubAction.options = [outputMsgToS3:true]
        sendEvent(name: "hubactionMode", value: "local");
    }
	hubAction
}

//Parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
    
    def map = [:]
    def retResult = []
    def descMap = parseDescriptionAsMap(description)
        
    //Image
	if (descMap["bucket"] && descMap["key"]) {
		putImageInS3(descMap)
	}

	//Status Polling
    else if (descMap["headers"] && descMap["body"]) {
        def body = new String(descMap["body"].decodeBase64())
        if(hdcamera == "true") {
            def langs = new XmlSlurper().parseText(body)

            def motionAlarm = "$langs.isEnable"
            def ledMode = "$langs.mode"

            //Get Motion Alarm Status
            if(motionAlarm == "0") {
                log.info("Polled: Alarm Off")
                sendEvent(name: "alarmStatus", value: "off");
            }
            else if(motionAlarm == "1") {
                log.info("Polled: Alarm On")
                sendEvent(name: "alarmStatus", value: "on");
            }

            //Get IR LED Mode
            if(ledMode == "0") {
                log.info("Polled: LED Mode Auto")
                sendEvent(name: "ledStatus", value: "auto")
            }
            else if(ledMode == "1") {
                log.info("Polled: LED Mode Manual")
                sendEvent(name: "ledStatus", value: "manual")
            }
    	}
        else {
        	if(body.find("alarm_motion_armed=0")) {
				log.info("Polled: Alarm Off")
                sendEvent(name: "alarmStatus", value: "off")
            }
        	else if(body.find("alarm_motion_armed=1")) {
				log.info("Polled: Alarm On")
                sendEvent(name: "alarmStatus", value: "on")
            }
            //The API does not provide a way to poll for LED status on 8xxx series at the moment
        }
	}
}

def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
}

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() }
	}
}

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

private getHostAddress() {
	return "${ip}:${port}"
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex

}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    return hexport
}
1 Like

@tslagle13 Nice work. The modification will only work for the non-HD cameras though. I’ve been holding off adding this stuff in because the HD cameras use the actual preset names which are user set so the code gets a little messy adding both versions in. I’ll get to it eventually. Also I’m hoping they get the video working soon in the carousel so that the PTZ controls are a little more useful.

Yeah i realized that. But hey, if someone wants it it’s there:) thats the nice thing about user communities right? :smile:

I wanted it, so I made it happen. gotta love it

I installed, set up the device type, but I’m unable to get anything to show in my “Things” view. I set all my preferences, internal IP, HTTP port, operator name/password, and HD camera true. I have 9821 V2 HD camera. I can push the buttons in things view, but they don’t seem to do anything.

Thanks!

@pitchdarkice Are you able to connect to your device externally through a browser? Are there any errors in the log when pressing the buttons?

I see various debug entries

Executing hubaction on ip…
Parsing 'index:01, …

But nothing shows up in the app

Yes I am able to connect externally via browser to the internal IP.

Thanks!

Can you verify a couple things…

  1. Did the Device Network Id change to the hex version of the IP
    address?
  2. Do you have your hub selected in the device settings
  3. Did you select the HD Foscam Camera option?
  1. Yes
  2. Yes
  3. Yes

thanks for your continued assistance.

@pitchdarkice Can you post more of the log? Mask out your personal information.

This works nicely with my 8910w on my local network… thanks @eparkerjr and @tslagle13! Will this also work remotely if I use ddns?

Thanks for the awesome work, love the fact that it works with the internal IP address.

To get it working with my FI9821W V2 I had to change the following lines from the original source:

200: return “usr=${username}&pwd=${password}&”

217: uri = apiCommand + getLogin()

I’m using the latest firmware available as of 8/9/2014. When the command string is before the credentials, it results in the following reply:

<CGI_Result>
-2
</CGI_Result>

So after your code change the url path goes from:
/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=user&pwd=password

to

/cgi-bin/CGIProxy.fcgi?usr=user&pwd=password&cmd=snapPicture2

and all is good.

Thanks again and amazing job!

@jdrorrer Right now this is only for internal use. There is a Foscam device type by imbrian in the forums that will work for external use.

@jscoleman That’s weird. I have 3 FI9821W V2 cameras and they all work for me with the newest firmware. I’ll swap it around in the next update just to avoid problems for anyone else.

Updated with PTZ controls. I know SmartThings is working on an official device type for Foscam cameras, so if you guys feel like pulling anything out my device type to add to it then feel free.

Should this also work with a FI8908W? Not having luck with it.

@sgonsalves It should work. I have a FI8906W and FI8910W and it works with both of those. Can you confirm you have a hub selected with the right internal IP and port?

With a FI9821W V2 when the alarm activates it removes other alarm configuration like schedule, email, sensitivity etc. Is this just me? Using the same device type works fine for email alarms on my other two non-HD cameras turning on and off the alarm.

@acastal Someone pointed this out to me last week also. Looks like it’s an issue with the HD cameras. I haven’t had a time to really look at it but I will probably end up adding the settings into the preferences.

I just registered to ask a question. First awesome work getting this to work with Foscam cameras! I have a few Loftek cameras that are similar (PTZ, motion, etc…).

I have a pretty good idea of what needs to be changed in this code. But what do I do with the code when done? How do I integrate it into smart things?

Thanks!