Update speaker-mood-music.groovy
[smartapps.git] / official / foscam-connect.groovy
1 /**
2  *  Copyright 2015 SmartThings
3  *
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:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
12  *
13  *  Foscam (connect)
14  *
15  *  Author: smartthings
16  *  Date: 2014-03-10
17  */
18
19 definition(
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",
27     singleInstance: true
28 )
29
30 preferences {
31         page(name: "cameraDiscovery", title:"Foscam Camera Setup", content:"cameraDiscovery")
32         page(name: "loginToFoscam", title: "Foscam Login")
33 }
34
35 //PAGES
36 /////////////////////////////////////
37 def cameraDiscovery()
38 {
39         if(canInstallLabs())
40         {
41                 int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
42                 state.refreshCount = refreshCount + 1
43                 def refreshInterval = 3
44
45                 def options = camerasDiscovered() ?: []
46                 def numFound = options.size() ?: 0
47
48                 if(!state.subscribe) {
49                         subscribe(location, null, locationHandler, [filterEvents:false])
50                         state.subscribe = true
51                 }
52
53                 //bridge discovery request every
54                 if((refreshCount % 5) == 0) {
55                         discoverCameras()
56                 }
57
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
61                         }
62                 }
63         }
64         else
65         {
66                 def upgradeNeeded = """To use Foscam, your Hub should be completely up to date.
67
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"."""
69
70                 return dynamicPage(name:"cameraDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
71                         section("Upgrade") {
72                                 paragraph "$upgradeNeeded"
73                         }
74                 }
75
76         }
77 }
78
79 def loginToFoscam() {
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
85                 }
86         }
87 }
88 //END PAGES
89
90 /////////////////////////////////////
91 private discoverCameras()
92 {
93         //add type UDP_CLIENT
94         def action = new physicalgraph.device.HubAction("0b4D4F5F490000000000000000000000040000000400000000000001", physicalgraph.device.Protocol.LAN, "FFFFFFFF:2710")
95         action.options = [type:"LAN_TYPE_UDPCLIENT"]
96         sendHubCommand(action)
97 }
98
99 def camerasDiscovered() {
100         def cameras = getCameras()
101         def map = [:]
102         cameras.each {
103                 def value = it.value.name ?: "Foscam Camera"
104                 def key = it.value.ip + ":" + it.value.port
105                 map["${key}"] = value
106         }
107         map
108 }
109
110 /////////////////////////////////////
111 def getCameras()
112 {
113         state.cameras = state.cameras ?: [:]
114 }
115
116 /////////////////////////////////////
117 def installed() {
118         //log.debug "Installed with settings: ${settings}"
119         initialize()
120
121         runIn(300, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 5 minutes
122
123         //wait 5 seconds and get the deviceInfo
124         //log.info "calling 'getDeviceInfo()'"
125         //runIn(5, getDeviceInfo)
126 }
127
128 /////////////////////////////////////
129 def updated() {
130         //log.debug "Updated with settings: ${settings}"
131         unsubscribe()
132         initialize()
133 }
134
135 /////////////////////////////////////
136 def initialize() {
137         // remove location subscription aftwards
138         unsubscribe()
139         state.subscribe = false
140
141         if (selectedFoscam)
142         {
143                 addCameras()
144         }
145 }
146
147 def addCameras() {
148         def cameras = getCameras()
149
150         selectedFoscam.each { dni ->
151                 def d = getChildDevice(dni)
152
153                 if(!d)
154                 {
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]])
157
158                         log.debug "created ${d.displayName} with id $dni"
159                 }
160                 else
161                 {
162                         log.debug "found ${d.displayName} with id $dni already exists"
163                 }
164         }
165 }
166
167 def getDeviceInfo() {
168         def devices = getAllChildDevices()
169         devices.each { d ->
170                 d.getDeviceInfo()
171         }
172 }
173
174 /////////////////////////////////////
175 def locationHandler(evt) {
176         /*
177         FOSCAM EXAMPLE
178         4D4F5F4901000000000000000000006200000000000000 (SOF) //46
179         30303632364534443042344200 (mac) //26
180         466F7363616D5F44617274684D61756C0000000000 (name) //42
181         0A01652C (ip) //8
182         FFFFFE00 (mask) //8
183         00000000 (gateway ip) //8
184         00000000 (dns) //8
185         01005800 (reserve) //8
186         01040108 (system software version) //8
187         020B0106 (app software version) //8
188         0058 (port) //4
189         01 (dhcp enabled) //2
190         */
191         def description = evt.description
192         def hub = evt?.hubId
193
194         log.debug "GOT LOCATION EVT: $description"
195
196         def parsedEvent = stringToMap(description)
197
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"))
200         {
201                 def unpacked = [:]
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]
213                 unpacked.hub = hub
214
215                 def cameras = getCameras()
216                 if (!(cameras."${parsedEvent.mac.toString()}"))
217                 {
218                         cameras << [("${parsedEvent.mac.toString()}"):unpacked]
219                 }
220         }
221 }
222
223 /////////////////////////////////////
224 private Boolean canInstallLabs()
225 {
226         return hasAllHubsOver("000.011.00603")
227 }
228
229 private Boolean hasAllHubsOver(String desiredFirmware)
230 {
231         return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
232 }
233
234 private List getRealHubFirmwareVersions()
235 {
236         return location.hubs*.firmwareVersionString.findAll { it }
237 }
238
239 private String hexToString(String txtInHex)
240 {
241         byte [] txtInByte = new byte [txtInHex.length() / 2];
242         int j = 0;
243         for (int i = 0; i < txtInHex.length(); i += 2)
244         {
245                         txtInByte[j++] = Byte.parseByte(txtInHex.substring(i, i + 2), 16);
246         }
247         return new String(txtInByte);
248 }