Update smart-alarm.groovy
[smartapps.git] / third-party / initialstate-smart-app-v1.2.0.groovy
1 /**
2  *  Initial State Event Streamer
3  *
4  *  Copyright 2016-2017 David Sulpy
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7  *  in compliance with the License. You may obtain a copy of the License at:
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13  *  for the specific language governing permissions and limitations under the License.
14  *
15  *  SmartThings data is sent from this SmartApp to Initial State. This is event data only for
16  *  devices for which the user has authorized. Likewise, Initial State's services call this
17  *  SmartApp on the user's behalf to configure Initial State specific parameters. The ToS and
18  *  Privacy Policy for Initial State can be found here: https://www.initialstate.com/terms
19  */
20
21 definition(
22     name: "Initial State Event Streamer",
23     namespace: "initialstate.events",
24     author: "David Sulpy",
25     description: "A SmartThings SmartApp to allow SmartThings events to be viewable inside an Initial State Event Bucket in your https://www.initialstate.com account.",
26     category: "SmartThings Labs",
27     iconUrl: "https://s3.amazonaws.com/initialstate-web-cdn/IS-wordmark-vertica_small.png",
28     iconX2Url: "https://s3.amazonaws.com/initialstate-web-cdn/IS-wordmark-vertical.png",
29     iconX3Url: "https://s3.amazonaws.com/initialstate-web-cdn/IS-wordmark-vertical.png",
30     oauth: [displayName: "Initial State", displayLink: "https://www.initialstate.com"])
31
32 import groovy.json.JsonSlurper
33
34 preferences {
35         section("Choose which devices to monitor...") {
36         input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
37         input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
38         input "batteries", "capability.battery", title: "Batteries", multiple: true, required: false
39         input "beacons", "capability.beacon", title: "Beacons", multiple: true, required: false
40         input "cos", "capability.carbonMonoxideDetector", title: "Carbon  Monoxide Detectors", multiple: true, required: false
41         input "colors", "capability.colorControl", title: "Color Controllers", multiple: true, required: false
42         input "contacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
43         input "doorsControllers", "capability.doorControl", title: "Door Controllers", multiple: true, required: false
44         input "energyMeters", "capability.energyMeter", title: "Energy Meters", multiple: true, required: false
45         input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance Meters", multiple: true, required: false
46         input "locks", "capability.lock", title: "Locks", multiple: true, required: false
47         input "motions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
48         input "musicPlayers", "capability.musicPlayer", title: "Music Players", multiple: true, required: false
49         input "powerMeters", "capability.powerMeter", title: "Power Meters", multiple: true, required: false
50         input "presences", "capability.presenceSensor", title: "Presence Sensors", multiple: true, required: false
51         input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity Meters", multiple: true, required: false
52         input "relaySwitches", "capability.relaySwitch", title: "Relay Switches", multiple: true, required: false
53         input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
54         input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
55         input "peds", "capability.stepSensor", title: "Pedometers", multiple: true, required: false
56         input "switches", "capability.switch", title: "Switches", multiple: true, required: false
57         input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
58         input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
59         input "thermostats", "capability.thermostat", title: "Thermostats", multiple: true, required: false
60         input "valves", "capability.valve", title: "Valves", multiple: true, required: false
61         input "waterSensors", "capability.waterSensor", title: "Water Sensors", multiple: true, required: false
62     }
63 }
64
65 mappings {
66         path("/access_key") {
67                 action: [
68                         GET: "getAccessKey",
69                         PUT: "setAccessKey"
70                 ]
71         }
72         path("/bucket") {
73                 action: [
74                         GET: "getBucketKey",
75                         PUT: "setBucketKey"
76                 ]
77         }
78     path("/disable") {
79         action: [
80                 POST: "disableIntegration"
81         ]
82     }
83     path("/enable") {
84         action: [
85                 POST: "enableIntegration"
86         ]
87     }
88 }
89
90 def disableIntegration() {
91         log.trace "disabling integration"
92     
93     atomicState.isDisabled = true
94     
95     [ isDisabled: atomicState.isDisabled ]
96 }
97
98 def enableIntegration() {
99         log.trace "enable integration"
100     
101     atomicState.isDisabled = false
102     
103     [ isDisabled: atomicState.isDisabled]
104 }
105
106 def getAccessKey() {
107         log.trace "get access key"
108         if (atomicState.accessKey == null) {
109                 httpError(404, "Access Key Not Found")
110         } else {
111                 [
112                         accessKey: atomicState.accessKey
113                 ]
114         }
115 }
116
117 def getBucketKey() {
118         log.trace "get bucket key"
119         if (atomicState.bucketKey == null) {
120                 httpError(404, "Bucket key Not Found")
121         } else {
122                 [
123                         bucketKey: atomicState.bucketKey,
124                         bucketName: atomicState.bucketName
125                 ]
126         }
127 }
128
129 def setBucketKey() {
130         log.trace "set bucket key"
131         def newBucketKey = request.JSON?.bucketKey
132         def newBucketName = request.JSON?.bucketName
133
134         log.debug "bucket name: $newBucketName"
135         log.debug "bucket key: $newBucketKey"
136
137         if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
138                 atomicState.bucketKey = "$newBucketKey"
139                 atomicState.bucketName = "$newBucketName"
140                 atomicState.isBucketCreated = false
141         }
142
143         tryCreateBucket()
144 }
145
146 def setAccessKey() {
147         log.trace "set access key"
148         def newAccessKey = request.JSON?.accessKey
149         def newGrokerSubdomain = request.JSON?.grokerSubdomain
150
151         if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
152                 atomicState.grokerSubdomain = "$newGrokerSubdomain"
153                 atomicState.isBucketCreated = false
154         }
155
156         if (newAccessKey && newAccessKey != atomicState.accessKey) {
157                 atomicState.accessKey = "$newAccessKey"
158                 atomicState.isBucketCreated = false
159         }
160 }
161
162 def subscribeToEvents() {
163         if (accelerometers != null) {
164                 subscribe(accelerometers, "acceleration", genericHandler)
165         }
166         if (alarms != null) {
167                 subscribe(alarms, "alarm", genericHandler)
168         }
169         if (batteries != null) {
170                 subscribe(batteries, "battery", genericHandler)
171         }
172         if (beacons != null) {
173                 subscribe(beacons, "presence", genericHandler)
174         }
175
176         if (cos != null) {
177                 subscribe(cos, "carbonMonoxide", genericHandler)
178         }
179         if (colors != null) {
180                 subscribe(colors, "hue", genericHandler)
181                 subscribe(colors, "saturation", genericHandler)
182                 subscribe(colors, "color", genericHandler)
183         }
184         if (contacts != null) {
185                 subscribe(contacts, "contact", genericHandler)
186         }
187         if (energyMeters != null) {
188                 subscribe(energyMeters, "energy", genericHandler)
189         }
190         if (illuminances != null) {
191                 subscribe(illuminances, "illuminance", genericHandler)
192         }
193         if (locks != null) {
194                 subscribe(locks, "lock", genericHandler)
195         }
196         if (motions != null) {
197                 subscribe(motions, "motion", genericHandler)
198         }
199         if (musicPlayers != null) {
200                 subscribe(musicPlayers, "status", genericHandler)
201                 subscribe(musicPlayers, "level", genericHandler)
202                 subscribe(musicPlayers, "trackDescription", genericHandler)
203                 subscribe(musicPlayers, "trackData", genericHandler)
204                 subscribe(musicPlayers, "mute", genericHandler)
205         }
206         if (powerMeters != null) {
207                 subscribe(powerMeters, "power", genericHandler)
208         }
209         if (presences != null) {
210                 subscribe(presences, "presence", genericHandler)
211         }
212         if (humidities != null) {
213                 subscribe(humidities, "humidity", genericHandler)
214         }
215         if (relaySwitches != null) {
216                 subscribe(relaySwitches, "switch", genericHandler)
217         }
218         if (sleepSensors != null) {
219                 subscribe(sleepSensors, "sleeping", genericHandler)
220         }
221         if (smokeDetectors != null) {
222                 subscribe(smokeDetectors, "smoke", genericHandler)
223         }
224         if (peds != null) {
225                 subscribe(peds, "steps", genericHandler)
226                 subscribe(peds, "goal", genericHandler)
227         }
228         if (switches != null) {
229                 subscribe(switches, "switch", genericHandler)
230         }
231         if (switchLevels != null) {
232                 subscribe(switchLevels, "level", genericHandler)
233         }
234         if (temperatures != null) {
235                 subscribe(temperatures, "temperature", genericHandler)
236         }
237         if (thermostats != null) {
238                 subscribe(thermostats, "temperature", genericHandler)
239                 subscribe(thermostats, "heatingSetpoint", genericHandler)
240                 subscribe(thermostats, "coolingSetpoint", genericHandler)
241                 subscribe(thermostats, "thermostatSetpoint", genericHandler)
242                 subscribe(thermostats, "thermostatMode", genericHandler)
243                 subscribe(thermostats, "thermostatFanMode", genericHandler)
244                 subscribe(thermostats, "thermostatOperatingState", genericHandler)
245         }
246         if (valves != null) {
247                 subscribe(valves, "contact", genericHandler)
248         }
249         if (waterSensors != null) {
250                 subscribe(waterSensors, "water", genericHandler)
251         }
252 }
253
254 def installed() {
255         atomicState.version = "1.2.0"
256
257         atomicState.isBucketCreated = false
258         atomicState.grokerSubdomain = "groker"
259     atomicState.isDisabled = false
260
261         subscribeToEvents()
262
263         atomicState.isBucketCreated = false
264         atomicState.grokerSubdomain = "groker"
265
266         log.debug "installed (version $atomicState.version)"
267 }
268
269 def updated() {
270         atomicState.version = "1.2.0"
271         unsubscribe()
272
273         if (atomicState.bucketKey != null && atomicState.accessKey != null) {
274                 atomicState.isBucketCreated = false
275         }
276         if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
277                 atomicState.grokerSubdomain = "groker"
278         }
279
280         subscribeToEvents()
281
282         log.debug "updated (version $atomicState.version)"
283 }
284
285 def uninstalled() {
286         log.debug "uninstalled (version $atomicState.version)"
287 }
288
289 def tryCreateBucket() {
290         // if the integration has been disabled, don't do anything
291         if (atomicState.isDisabled) {
292         log.debug "integration is disabled"
293         return
294     }
295
296         // can't ship events if there is no grokerSubdomain
297         if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
298                 log.error "streaming url is currently null"
299                 return
300         }
301
302         // if the bucket has already been created, no need to continue
303         if (atomicState.isBucketCreated) {
304                 return
305         }
306
307         if (!atomicState.bucketName) {
308         atomicState.bucketName = atomicState.bucketKey
309     }
310     if (!atomicState.accessKey) {
311         return
312     }
313         def bucketName = "${atomicState.bucketName}"
314         def bucketKey = "${atomicState.bucketKey}"
315         def accessKey = "${atomicState.accessKey}"
316
317         def bucketCreateBody = new JsonSlurper().parseText("{\"bucketKey\": \"$bucketKey\", \"bucketName\": \"$bucketName\"}")
318
319         def bucketCreatePost = [
320                 uri: "https://${atomicState.grokerSubdomain}.initialstate.com/api/buckets",
321                 headers: [
322                         "Content-Type": "application/json",
323                         "X-IS-AccessKey": accessKey
324                 ],
325                 body: bucketCreateBody
326         ]
327
328         log.debug bucketCreatePost
329
330         try {
331                 // Create a bucket on Initial State so the data has a logical grouping
332                 httpPostJson(bucketCreatePost) { resp ->
333                         log.debug "bucket posted"
334                         if (resp.status >= 400) {
335                                 log.error "bucket not created successfully"
336                         } else {
337                                 atomicState.isBucketCreated = true
338                         }
339                 }
340         } catch (e) {
341                 log.error "bucket creation error: $e"
342         }
343
344 }
345
346 def genericHandler(evt) {
347         log.trace "$evt.displayName($evt.name:$evt.unit) $evt.value"
348
349         def key = "$evt.displayName($evt.name)"
350         if (evt.unit != null) {
351                 key = "$evt.displayName(${evt.name}_$evt.unit)"
352         }
353         def value = "$evt.value"
354
355         tryCreateBucket()
356
357         eventHandler(key, value)
358 }
359
360 def eventHandler(name, value) {
361         def epoch = now() / 1000
362
363         def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
364
365         tryShipEvents(event)
366         
367         log.debug "Shipped Event: " + event
368 }
369
370 def tryShipEvents(event) {
371
372         // if the integration is not enabled, then don't attempt to send any data
373         if (atomicState.isDisabled) {
374         log.debug "not shipping, integration is disabled"
375         return
376     }
377
378         def grokerSubdomain = atomicState.grokerSubdomain
379         // can't ship events if there is no grokerSubdomain
380         if (grokerSubdomain == null || grokerSubdomain == "") {
381                 log.error "streaming url is currently null"
382                 return
383         }
384         def accessKey = atomicState.accessKey
385         def bucketKey = atomicState.bucketKey
386         // can't ship if access key and bucket key are null, so finish trying
387         if (accessKey == null || bucketKey == null) {
388                 return
389         }
390
391         def eventPost = [
392                 uri: "https://${grokerSubdomain}.initialstate.com/api/events",
393                 headers: [
394                         "Content-Type": "application/json",
395                         "X-IS-BucketKey": "${bucketKey}",
396                         "X-IS-AccessKey": "${accessKey}",
397                         "Accept-Version": "0.0.2"
398                 ],
399                 body: event
400         ]
401
402         try {
403                 // post the events to initial state
404                 httpPostJson(eventPost) { resp ->
405                         log.debug "shipped events and got ${resp.status}"
406                         if (resp.status >= 400) {
407                                 log.error "shipping failed... ${resp.data}"
408                         }
409                 }
410         } catch (e) {
411                 log.error "shipping events failed: $e"
412         }
413
414 }