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.
13 * IFTTT API Access Application
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 * ---------------------+----------------+--------------------------+------------------------------------
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"]
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
63 path("/:deviceType") {
68 path("/:deviceType/states") {
73 path("/:deviceType/subscription") {
75 POST: "addSubscription"
78 path("/:deviceType/subscriptions/:id") {
80 DELETE: "removeSubscription"
83 path("/:deviceType/:id") {
89 path("/subscriptions") {
91 GET: "listSubscriptions"
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)
105 subscriptionDevicesToRemove.each { device ->
106 log.debug "Removing $device.displayName subscription"
107 state.remove(device.id)
114 log.debug "[PROD] list, params: ${params}"
115 def type = params.deviceType
116 settings[type]?.collect{deviceItem(it)} ?: []
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))} ?: []
126 def listSubscriptions() {
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
137 log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}"
140 httpError(404, "Device not found")
143 if (validateCommand(device, type, command)) {
146 httpError(403, "Access denied. This command is not supported by current capability.")
151 * Validating the command passed by the user based on capability.
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
160 // Handling other device types here, which don't accept commands
161 httpError(400, "Bad request.")
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
170 def getCapabilityName(type) {
184 * Constructing the map over here of
185 * supported commands by device capability
186 * @return a map of device capability -> supported commands
188 def getDeviceCapabilityCommands(deviceCapabilities) {
190 deviceCapabilities.collect {
191 map[it.name] = it.commands.collect{ it.name.toString() }
198 def type = params.deviceType
199 def devices = settings[type]
200 def device = devices.find { it.id == params.id }
202 log.debug "[PROD] show, params: ${params}, devices: ${devices*.id}"
204 httpError(404, "Device not found")
207 def attributeName = attributeFor(type)
208 def s = device.currentState(attributeName)
209 deviceState(device, s)
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 }
223 log.debug "[PROD] addSubscription, params: ${params}, request: ${data}, device: ${device}"
225 log.debug "Adding switch subscription " + callbackUrl
226 state[deviceId] = [callbackUrl: callbackUrl]
227 subscribe(device, attribute, deviceHandler)
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 }
239 log.debug "[PROD] removeSubscription, params: ${params}, request: ${data}, device: ${device}"
241 log.debug "Removing $device.displayName subscription"
242 state.remove(device.id)
248 def deviceHandler(evt) {
249 def deviceInfo = state[evt.deviceId]
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"
255 } catch (groovyx.net.http.ResponseParseException e) {
256 log.debug("Error parsing ifttt payload ${e}")
259 log.debug "[PROD] No subscribed device found"
263 private deviceItem(it) {
264 it ? [id: it.id, label: it.displayName] : null
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
271 private attributeFor(type) {
274 log.debug "[PROD] switch type"
277 log.debug "[PROD] lock type"
280 log.debug "[PROD] alarm type"
283 log.debug "[PROD] illuminance type"
286 log.debug "[PROD] other sensor type"
287 return type - "Sensors"