2 * Copyright 2015 SmartThings
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11 * for the specific language governing permissions and limitations under the License.
20 name: "Foscam (Connect)",
21 namespace: "smartthings",
22 author: "SmartThings",
23 description: "Connect and take pictures using your Foscam camera from inside the Smartthings app.",
24 category: "SmartThings Internal",
25 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam.png",
26 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png",
31 page(name: "cameraDiscovery", title:"Foscam Camera Setup", content:"cameraDiscovery")
32 page(name: "loginToFoscam", title: "Foscam Login")
36 /////////////////////////////////////
41 int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
42 state.refreshCount = refreshCount + 1
43 def refreshInterval = 3
45 def options = camerasDiscovered() ?: []
46 def numFound = options.size() ?: 0
48 if(!state.subscribe) {
49 subscribe(location, null, locationHandler, [filterEvents:false])
50 state.subscribe = true
53 //bridge discovery request every
54 if((refreshCount % 5) == 0) {
58 return dynamicPage(name:"cameraDiscovery", title:"Discovery Started!", nextPage:"loginToFoscam", refreshInterval:refreshInterval, uninstall: true) {
59 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.") {
60 input "selectedFoscam", "enum", required:false, title:"Select Foscam (${numFound} found)", multiple:true, options:options
66 def upgradeNeeded = """To use Foscam, your Hub should be completely up to date.
68 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"."""
70 return dynamicPage(name:"cameraDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
72 paragraph "$upgradeNeeded"
80 def showUninstall = username != null && password != null
81 return dynamicPage(name: "loginToFoscam", title: "Foscam", uninstall:showUninstall, install:true,) {
82 section("Log in to Foscam") {
83 input "username", "text", title: "Username", required: true, autoCorrect:false
84 input "password", "password", title: "Password", required: true, autoCorrect:false
90 /////////////////////////////////////
91 private discoverCameras()
94 def action = new physicalgraph.device.HubAction("0b4D4F5F490000000000000000000000040000000400000000000001", physicalgraph.device.Protocol.LAN, "FFFFFFFF:2710")
95 action.options = [type:"LAN_TYPE_UDPCLIENT"]
96 sendHubCommand(action)
99 def camerasDiscovered() {
100 def cameras = getCameras()
103 def value = it.value.name ?: "Foscam Camera"
104 def key = it.value.ip + ":" + it.value.port
105 map["${key}"] = value
110 /////////////////////////////////////
113 state.cameras = state.cameras ?: [:]
116 /////////////////////////////////////
118 //log.debug "Installed with settings: ${settings}"
121 runIn(300, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 5 minutes
123 //wait 5 seconds and get the deviceInfo
124 //log.info "calling 'getDeviceInfo()'"
125 //runIn(5, getDeviceInfo)
128 /////////////////////////////////////
130 //log.debug "Updated with settings: ${settings}"
135 /////////////////////////////////////
137 // remove location subscription aftwards
139 state.subscribe = false
148 def cameras = getCameras()
150 selectedFoscam.each { dni ->
151 def d = getChildDevice(dni)
155 def newFoscam = cameras.find { (it.value.ip + ":" + it.value.port) == dni }
156 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]])
158 log.debug "created ${d.displayName} with id $dni"
162 log.debug "found ${d.displayName} with id $dni already exists"
167 def getDeviceInfo() {
168 def devices = getAllChildDevices()
174 /////////////////////////////////////
175 def locationHandler(evt) {
178 4D4F5F4901000000000000000000006200000000000000 (SOF) //46
179 30303632364534443042344200 (mac) //26
180 466F7363616D5F44617274684D61756C0000000000 (name) //42
183 00000000 (gateway ip) //8
185 01005800 (reserve) //8
186 01040108 (system software version) //8
187 020B0106 (app software version) //8
189 01 (dhcp enabled) //2
191 def description = evt.description
194 log.debug "GOT LOCATION EVT: $description"
196 def parsedEvent = stringToMap(description)
198 //FOSCAM does a UDP response with camera operate protocol:“MO_I” i.e. "4D4F5F49"
199 if (parsedEvent?.type == "LAN_TYPE_UDPCLIENT" && parsedEvent?.payload?.startsWith("4D4F5F49"))
202 unpacked.mac = parsedEvent.mac.toString()
203 unpacked.name = hexToString(parsedEvent.payload[72..113]).trim()
204 unpacked.ip = parsedEvent.payload[114..121]
205 unpacked.subnet = parsedEvent.payload[122..129]
206 unpacked.gateway = parsedEvent.payload[130..137]
207 unpacked.dns = parsedEvent.payload[138..145]
208 unpacked.reserve = parsedEvent.payload[146..153]
209 unpacked.sysVersion = parsedEvent.payload[154..161]
210 unpacked.appVersion = parsedEvent.payload[162..169]
211 unpacked.port = parsedEvent.payload[170..173]
212 unpacked.dhcp = parsedEvent.payload[174..175]
215 def cameras = getCameras()
216 if (!(cameras."${parsedEvent.mac.toString()}"))
218 cameras << [("${parsedEvent.mac.toString()}"):unpacked]
223 /////////////////////////////////////
224 private Boolean canInstallLabs()
226 return hasAllHubsOver("000.011.00603")
229 private Boolean hasAllHubsOver(String desiredFirmware)
231 return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
234 private List getRealHubFirmwareVersions()
236 return location.hubs*.firmwareVersionString.findAll { it }
239 private String hexToString(String txtInHex)
241 byte [] txtInByte = new byte [txtInHex.length() / 2];
243 for (int i = 0; i < txtInHex.length(); i += 2)
245 txtInByte[j++] = Byte.parseByte(txtInHex.substring(i, i + 2), 16);
247 return new String(txtInByte);