/** * 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 (connect) * * Author: smartthings * Date: 2014-03-10 */ definition( name: "Foscam (Connect)", namespace: "smartthings", author: "SmartThings", description: "Connect and take pictures using your Foscam camera from inside the Smartthings app.", category: "SmartThings Internal", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png", singleInstance: true ) preferences { page(name: "cameraDiscovery", title:"Foscam Camera Setup", content:"cameraDiscovery") page(name: "loginToFoscam", title: "Foscam Login") } //PAGES ///////////////////////////////////// def cameraDiscovery() { if(canInstallLabs()) { int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int state.refreshCount = refreshCount + 1 def refreshInterval = 3 def options = camerasDiscovered() ?: [] def numFound = options.size() ?: 0 if(!state.subscribe) { subscribe(location, null, locationHandler, [filterEvents:false]) state.subscribe = true } //bridge discovery request every if((refreshCount % 5) == 0) { discoverCameras() } return dynamicPage(name:"cameraDiscovery", title:"Discovery Started!", nextPage:"loginToFoscam", refreshInterval:refreshInterval, uninstall: true) { section("Please wait while we discover your Foscam. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { input "selectedFoscam", "enum", required:false, title:"Select Foscam (${numFound} found)", multiple:true, options:options } } } else { def upgradeNeeded = """To use Foscam, your Hub should be completely up to date. To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub".""" return dynamicPage(name:"cameraDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) { section("Upgrade") { paragraph "$upgradeNeeded" } } } } def loginToFoscam() { def showUninstall = username != null && password != null return dynamicPage(name: "loginToFoscam", title: "Foscam", uninstall:showUninstall, install:true,) { section("Log in to Foscam") { input "username", "text", title: "Username", required: true, autoCorrect:false input "password", "password", title: "Password", required: true, autoCorrect:false } } } //END PAGES ///////////////////////////////////// private discoverCameras() { //add type UDP_CLIENT def action = new physicalgraph.device.HubAction("0b4D4F5F490000000000000000000000040000000400000000000001", physicalgraph.device.Protocol.LAN, "FFFFFFFF:2710") action.options = [type:"LAN_TYPE_UDPCLIENT"] sendHubCommand(action) } def camerasDiscovered() { def cameras = getCameras() def map = [:] cameras.each { def value = it.value.name ?: "Foscam Camera" def key = it.value.ip + ":" + it.value.port map["${key}"] = value } map } ///////////////////////////////////// def getCameras() { state.cameras = state.cameras ?: [:] } ///////////////////////////////////// def installed() { //log.debug "Installed with settings: ${settings}" initialize() runIn(300, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 5 minutes //wait 5 seconds and get the deviceInfo //log.info "calling 'getDeviceInfo()'" //runIn(5, getDeviceInfo) } ///////////////////////////////////// def updated() { //log.debug "Updated with settings: ${settings}" unsubscribe() initialize() } ///////////////////////////////////// def initialize() { // remove location subscription aftwards unsubscribe() state.subscribe = false if (selectedFoscam) { addCameras() } } def addCameras() { def cameras = getCameras() selectedFoscam.each { dni -> def d = getChildDevice(dni) if(!d) { def newFoscam = cameras.find { (it.value.ip + ":" + it.value.port) == dni } d = addChildDevice("smartthings", "Foscam", dni, newFoscam?.value?.hub, ["label":newFoscam?.value?.name ?: "Foscam Camera", "data":["mac": newFoscam?.value?.mac, "ip": newFoscam.value.ip, "port":newFoscam.value.port], "preferences":["username":username, "password":password]]) log.debug "created ${d.displayName} with id $dni" } else { log.debug "found ${d.displayName} with id $dni already exists" } } } def getDeviceInfo() { def devices = getAllChildDevices() devices.each { d -> d.getDeviceInfo() } } ///////////////////////////////////// def locationHandler(evt) { /* FOSCAM EXAMPLE 4D4F5F4901000000000000000000006200000000000000 (SOF) //46 30303632364534443042344200 (mac) //26 466F7363616D5F44617274684D61756C0000000000 (name) //42 0A01652C (ip) //8 FFFFFE00 (mask) //8 00000000 (gateway ip) //8 00000000 (dns) //8 01005800 (reserve) //8 01040108 (system software version) //8 020B0106 (app software version) //8 0058 (port) //4 01 (dhcp enabled) //2 */ def description = evt.description def hub = evt?.hubId log.debug "GOT LOCATION EVT: $description" def parsedEvent = stringToMap(description) //FOSCAM does a UDP response with camera operate protocol:“MO_I” i.e. "4D4F5F49" if (parsedEvent?.type == "LAN_TYPE_UDPCLIENT" && parsedEvent?.payload?.startsWith("4D4F5F49")) { def unpacked = [:] unpacked.mac = parsedEvent.mac.toString() unpacked.name = hexToString(parsedEvent.payload[72..113]).trim() unpacked.ip = parsedEvent.payload[114..121] unpacked.subnet = parsedEvent.payload[122..129] unpacked.gateway = parsedEvent.payload[130..137] unpacked.dns = parsedEvent.payload[138..145] unpacked.reserve = parsedEvent.payload[146..153] unpacked.sysVersion = parsedEvent.payload[154..161] unpacked.appVersion = parsedEvent.payload[162..169] unpacked.port = parsedEvent.payload[170..173] unpacked.dhcp = parsedEvent.payload[174..175] unpacked.hub = hub def cameras = getCameras() if (!(cameras."${parsedEvent.mac.toString()}")) { cameras << [("${parsedEvent.mac.toString()}"):unpacked] } } } ///////////////////////////////////// private Boolean canInstallLabs() { return hasAllHubsOver("000.011.00603") } private Boolean hasAllHubsOver(String desiredFirmware) { return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware } } private List getRealHubFirmwareVersions() { return location.hubs*.firmwareVersionString.findAll { it } } private String hexToString(String txtInHex) { byte [] txtInByte = new byte [txtInHex.length() / 2]; int j = 0; for (int i = 0; i < txtInHex.length(); i += 2) { txtInByte[j++] = Byte.parseByte(txtInHex.substring(i, i + 2), 16); } return new String(txtInByte); }