Skip to content

Instantly share code, notes, and snippets.

@ArthurGuy
Created January 30, 2017 16:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ArthurGuy/cd41b36b3dafc6173c9c212e4bc97446 to your computer and use it in GitHub Desktop.
Save ArthurGuy/cd41b36b3dafc6173c9c212e4bc97446 to your computer and use it in GitHub Desktop.
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Foscam
*
* Author: SmartThings
* Date: 2014-02-04
*/
metadata {
definition (name: "Foscam", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Sensor"
capability "Image Capture"
}
simulator {
// TODO: define status and reply messages here
}
//TODO:encrypt these settings and make them required:true
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", "text", title: "Username", description: "Your Foscam Username", required: false
input "password", "password", title: "Password", description: "Your Foscam Password", required: false
}
tiles {
standardTile("image", "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.dropcam", backgroundColor: "#FFFFFF", nextState:"taking"
state "taking", label:'Taking', action: "", icon: "st.camera.dropcam", backgroundColor: "#53a7c0"
state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.dropcam", backgroundColor: "#FFFFFF", nextState:"taking"
}
/*standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"getDeviceInfo", icon:"st.secondary.refresh"
}*/
main "image"
details(["cameraDetails", "take"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
def map = stringToMap(description)
log.debug map
def result = []
if (map.key)
{ //got a s3 pointer
try {
storeTemporaryImage(map.key, getPictureName())
} catch(Exception e) {
log.error e
}
}
else if (map.headers && map.body)
{ //got device info response
/*
TODO:need to figure out a way to reliable know which end the snapshot should be taken at.
Current theory is that 8xxx series cameras are at /snapshot.cgi and 9xxx series are at /cgi-bin/CGIProxy.fcgi
*/
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 "Got Foscam Product Name: $productName"
state.snapshot = "/cgi-bin/CGIProxy.fcgi"
}
}
}
result
}
// handle commands
def take() {
log.debug "Executing 'take'"
//Snapshot uri depends on model number:
//because 8 series uses user and 9 series uses usr -
//try based on port since issuing a GET with usr to 8 series causes it throw 401 until you reauthorize using basic digest authentication
def host = getHostAddress()
def path = "/cgi-bin/CGIProxy.fcgi?usr=${getUsername()}&pwd=${getPassword()}&cmd=snapPicture2"
log.debug host + path
def hubAction = new physicalgraph.device.HubAction(
method: "GET",
path: path,
headers: [HOST:host]
)
hubAction.options = [outputMsgToS3:true]
hubAction
}
/*def getDeviceInfo() {
log.debug "Executing 'getDeviceInfo'"
def path = "/cgi-bin/CGIProxy.fcgi"
def hubAction = new physicalgraph.device.HubAction(
method: "GET",
path: path,
headers: [HOST:getHostAddress()],
query:[cmd:"getDevInfo", usr:getUsername(), pwd:getPassword()]
)
}*/
//helper methods
private getPictureName() {
def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
return "image" + "_$pictureUuid" + ".jpg"
}
private getUsername() {
settings.username
}
private getPassword() {
settings.password
}
private Integer convertHexToInt(hex) {
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() {
return "${settings.ip}:${settings.port}"
}
//private getHostAddress() {
// def parts = device.deviceNetworkId.split(":")
// def ip = convertHexToIP(parts[0])
// def port = convertHexToInt(parts[1])
// return ip + ":" + port
//}
private hashMD5(String somethingToHash) {
java.security.MessageDigest.getInstance("MD5").digest(somethingToHash.getBytes("UTF-8")).encodeHex().toString()
}
private calcDigestAuth(String method, String uri) {
def HA1 = hashMD5("${getUsername}::${getPassword}")
def HA2 = hashMD5("${method}:${uri}")
def response = hashMD5("${HA1}::::auth:${HA2}")
'Digest username="'+ getUsername() + '", realm="", nonce="", uri="'+ uri +'", qop=auth, nc=, cnonce="", response="' + response + '", opaque=""'
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment