Changing remote branch to PLRG Git server.
[smartapps.git] / official / ifttt.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  *  IFTTT API Access Application
14  *
15  *  Author: SmartThings
16  *
17  *  ---------------------+----------------+--------------------------+------------------------------------
18  *  Device Type          | Attribute Name | Commands                 | Attribute Values
19  *  ---------------------+----------------+--------------------------+------------------------------------
20  *  switches             | switch         | on, off                  | on, off
21  *  motionSensors        | motion         |                          | active, inactive
22  *  contactSensors       | contact        |                          | open, closed
23  *  presenceSensors      | presence       |                          | present, 'not present'
24  *  temperatureSensors   | temperature    |                          | <numeric, F or C according to unit>
25  *  accelerationSensors  | acceleration   |                          | active, inactive
26  *  waterSensors         | water          |                          | wet, dry
27  *  lightSensors         | illuminance    |                          | <numeric, lux>
28  *  humiditySensors      | humidity       |                          | <numeric, percent>
29  *  alarms               | alarm          | strobe, siren, both, off | strobe, siren, both, off
30  *  locks                | lock           | lock, unlock             | locked, unlocked
31  *  ---------------------+----------------+--------------------------+------------------------------------
32  */
33
34 definition(
35     name: "IFTTT",
36     namespace: "smartthings",
37     author: "SmartThings",
38     description: "Put the internet to work for you.",
39     category: "SmartThings Internal",
40     iconUrl: "https://ifttt.com/images/channels/ifttt.png",
41     iconX2Url: "https://ifttt.com/images/channels/ifttt_med.png",
42     oauth: [displayName: "IFTTT", displayLink: "https://ifttt.com"]
43 )
44
45 preferences {
46         section("Allow IFTTT to control these things...") {
47                 input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
48                 input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
49                 input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
50                 input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
51                 input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
52                 input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
53                 input "waterSensors", "capability.waterSensor", title: "Which Water Sensors?", multiple: true, required: false
54                 input "lightSensors", "capability.illuminanceMeasurement", title: "Which Light Sensors?", multiple: true, required: false
55                 input "humiditySensors", "capability.relativeHumidityMeasurement", title: "Which Relative Humidity Sensors?", multiple: true, required: false
56                 input "alarms", "capability.alarm", title: "Which Sirens?", multiple: true, required: false
57                 input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
58         }
59 }
60
61 mappings {
62
63         path("/:deviceType") {
64                 action: [
65                         GET: "list"
66                 ]
67         }
68         path("/:deviceType/states") {
69                 action: [
70                         GET: "listStates"
71                 ]
72         }
73         path("/:deviceType/subscription") {
74                 action: [
75                         POST: "addSubscription"
76                 ]
77         }
78         path("/:deviceType/subscriptions/:id") {
79                 action: [
80                         DELETE: "removeSubscription"
81                 ]
82         }
83         path("/:deviceType/:id") {
84                 action: [
85                         GET: "show",
86                         PUT: "update"
87                 ]
88         }
89         path("/subscriptions") {
90                 action: [
91                         GET: "listSubscriptions"
92                 ]
93         }
94 }
95
96 def installed() {
97         log.debug settings
98 }
99
100 def updated() {
101         def currentDeviceIds = settings.collect { k, devices -> devices }.flatten().collect { it.id }.unique()
102         def subscriptionDevicesToRemove = app.subscriptions*.device.findAll { device ->
103                 !currentDeviceIds.contains(device.id)
104         }
105         subscriptionDevicesToRemove.each { device ->
106                 log.debug "Removing $device.displayName subscription"
107                 state.remove(device.id)
108                 unsubscribe(device)
109         }
110         log.debug settings
111 }
112
113 def list() {
114         log.debug "[PROD] list, params: ${params}"
115         def type = params.deviceType
116         settings[type]?.collect{deviceItem(it)} ?: []
117 }
118
119 def listStates() {
120         log.debug "[PROD] states, params: ${params}"
121         def type = params.deviceType
122         def attributeName = attributeFor(type)
123         settings[type]?.collect{deviceState(it, it.currentState(attributeName))} ?: []
124 }
125
126 def listSubscriptions() {
127         state
128 }
129
130 def update() {
131         def type = params.deviceType
132         def data = request.JSON
133         def devices = settings[type]
134         def device = settings[type]?.find { it.id == params.id }
135         def command = data.command
136
137         log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}"
138         
139         if (!device) {
140                 httpError(404, "Device not found")
141         } 
142         
143         if (validateCommand(device, type, command)) {
144                 device."$command"()
145         } else {
146                 httpError(403, "Access denied. This command is not supported by current capability.")
147         }
148 }
149
150 /**
151  * Validating the command passed by the user based on capability.
152  * @return boolean
153  */
154 def validateCommand(device, deviceType, command) {
155         def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
156         def currentDeviceCapability = getCapabilityName(deviceType)
157         if (capabilityCommands[currentDeviceCapability]) {
158                 return command in capabilityCommands[currentDeviceCapability] ? true : false
159         } else {
160                 // Handling other device types here, which don't accept commands
161                 httpError(400, "Bad request.")
162         }
163 }
164
165 /**
166  * Need to get the attribute name to do the lookup. Only
167  * doing it for the device types which accept commands
168  * @return attribute name of the device type
169  */
170 def getCapabilityName(type) {
171     switch(type) {
172                 case "switches":
173                         return "Switch"
174                 case "alarms":
175                         return "Alarm"
176                 case "locks":
177                         return "Lock"
178                 default:
179                         return type
180         }
181 }
182
183 /**
184  * Constructing the map over here of
185  * supported commands by device capability
186  * @return a map of device capability -> supported commands
187  */
188 def getDeviceCapabilityCommands(deviceCapabilities) {
189         def map = [:]
190         deviceCapabilities.collect {
191                 map[it.name] = it.commands.collect{ it.name.toString() }
192         }
193         return map
194 }
195
196
197 def show() {
198         def type = params.deviceType
199         def devices = settings[type]
200         def device = devices.find { it.id == params.id }
201
202         log.debug "[PROD] show, params: ${params}, devices: ${devices*.id}"
203         if (!device) {
204                 httpError(404, "Device not found")
205         }
206         else {
207                 def attributeName = attributeFor(type)
208                 def s = device.currentState(attributeName)
209                 deviceState(device, s)
210         }
211 }
212
213 def addSubscription() {
214         log.debug "[PROD] addSubscription1"
215         def type = params.deviceType
216         def data = request.JSON
217         def attribute = attributeFor(type)
218         def devices = settings[type]
219         def deviceId = data.deviceId
220         def callbackUrl = data.callbackUrl
221         def device = devices.find { it.id == deviceId }
222
223         log.debug "[PROD] addSubscription, params: ${params}, request: ${data}, device: ${device}"
224         if (device) {
225                 log.debug "Adding switch subscription " + callbackUrl
226                 state[deviceId] = [callbackUrl: callbackUrl]
227                 subscribe(device, attribute, deviceHandler)
228         }
229         log.info state
230
231 }
232
233 def removeSubscription() {
234         def type = params.deviceType
235         def devices = settings[type]
236         def deviceId = params.id
237         def device = devices.find { it.id == deviceId }
238
239         log.debug "[PROD] removeSubscription, params: ${params}, request: ${data}, device: ${device}"
240         if (device) {
241                 log.debug "Removing $device.displayName subscription"
242                 state.remove(device.id)
243                 unsubscribe(device)
244         }
245         log.info state
246 }
247
248 def deviceHandler(evt) {
249         def deviceInfo = state[evt.deviceId]
250         if (deviceInfo) {
251                 try {
252                         httpPostJson(uri: deviceInfo.callbackUrl, path: '',  body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]) {
253                                 log.debug "[PROD IFTTT] Event data successfully posted"
254                         }
255                 } catch (groovyx.net.http.ResponseParseException e) {
256                         log.debug("Error parsing ifttt payload ${e}")
257                 }
258         } else {
259                 log.debug "[PROD] No subscribed device found"
260         }
261 }
262
263 private deviceItem(it) {
264         it ? [id: it.id, label: it.displayName] : null
265 }
266
267 private deviceState(device, s) {
268         device && s ? [id: device.id, label: device.displayName, name: s.name, value: s.value, unixTime: s.date.time] : null
269 }
270
271 private attributeFor(type) {
272         switch (type) {
273                 case "switches":
274                         log.debug "[PROD] switch type"
275                         return "switch"
276                 case "locks":
277                         log.debug "[PROD] lock type"
278                         return "lock"
279                 case "alarms":
280                         log.debug "[PROD] alarm type"
281                         return "alarm"
282                 case "lightSensors":
283                         log.debug "[PROD] illuminance type"
284                         return "illuminance"
285                 default:
286                         log.debug "[PROD] other sensor type"
287                         return type - "Sensors"
288         }
289 }