2 * Initial State Event Streamer (non-buffered)
4 * Copyright 2016 David Sulpy
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:
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
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
22 name: "DIY 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"])
32 import groovy.json.JsonSlurper
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
65 def subscribeToEvents() {
66 if (accelerometers != null) {
67 subscribe(accelerometers, "acceleration", genericHandler)
70 subscribe(alarms, "alarm", genericHandler)
72 if (batteries != null) {
73 subscribe(batteries, "battery", genericHandler)
75 if (beacons != null) {
76 subscribe(beacons, "presence", genericHandler)
80 subscribe(cos, "carbonMonoxide", genericHandler)
83 subscribe(colors, "hue", genericHandler)
84 subscribe(colors, "saturation", genericHandler)
85 subscribe(colors, "color", genericHandler)
87 if (contacts != null) {
88 subscribe(contacts, "contact", genericHandler)
90 if (energyMeters != null) {
91 subscribe(energyMeters, "energy", genericHandler)
93 if (illuminances != null) {
94 subscribe(illuminances, "illuminance", genericHandler)
97 subscribe(locks, "lock", genericHandler)
99 if (motions != null) {
100 subscribe(motions, "motion", genericHandler)
102 if (musicPlayers != null) {
103 subscribe(musicPlayers, "status", genericHandler)
104 subscribe(musicPlayers, "level", genericHandler)
105 subscribe(musicPlayers, "trackDescription", genericHandler)
106 subscribe(musicPlayers, "trackData", genericHandler)
107 subscribe(musicPlayers, "mute", genericHandler)
109 if (powerMeters != null) {
110 subscribe(powerMeters, "power", genericHandler)
112 if (presences != null) {
113 subscribe(presences, "presence", genericHandler)
115 if (humidities != null) {
116 subscribe(humidities, "humidity", genericHandler)
118 if (relaySwitches != null) {
119 subscribe(relaySwitches, "switch", genericHandler)
121 if (sleepSensors != null) {
122 subscribe(sleepSensors, "sleeping", genericHandler)
124 if (smokeDetectors != null) {
125 subscribe(smokeDetectors, "smoke", genericHandler)
128 subscribe(peds, "steps", genericHandler)
129 subscribe(peds, "goal", genericHandler)
131 if (switches != null) {
132 subscribe(switches, "switch", genericHandler)
134 if (switchLevels != null) {
135 subscribe(switchLevels, "level", genericHandler)
137 if (temperatures != null) {
138 subscribe(temperatures, "temperature", genericHandler)
140 if (thermostats != null) {
141 subscribe(thermostats, "temperature", genericHandler)
142 subscribe(thermostats, "heatingSetpoint", genericHandler)
143 subscribe(thermostats, "coolingSetpoint", genericHandler)
144 subscribe(thermostats, "thermostatSetpoint", genericHandler)
145 subscribe(thermostats, "thermostatMode", genericHandler)
146 subscribe(thermostats, "thermostatFanMode", genericHandler)
147 subscribe(thermostats, "thermostatOperatingState", genericHandler)
149 if (valves != null) {
150 subscribe(valves, "contact", genericHandler)
152 if (waterSensors != null) {
153 subscribe(waterSensors, "water", genericHandler)
158 atomicState.version = "1.0.18 (unbuffered)"
160 atomicState.bucketKey = "SmartThings" //change if needed
161 atomicState.bucketName = "SmartThings" //change if wanted
162 atomicState.accessKey = "YOUR_ACCESS_KEY" //MUST CHANGE
166 atomicState.isBucketCreated = false
167 atomicState.grokerSubdomain = "groker"
169 log.debug "installed (version $atomicState.version)"
173 atomicState.version = "1.0.18 (unbuffered)"
176 if (atomicState.bucketKey != null && atomicState.accessKey != null) {
177 atomicState.isBucketCreated = false
179 if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
180 atomicState.grokerSubdomain = "groker"
185 log.debug "updated (version $atomicState.version)"
189 log.debug "uninstalled (version $atomicState.version)"
192 def tryCreateBucket() {
194 // can't ship events if there is no grokerSubdomain
195 if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
196 log.error "streaming url is currently null"
200 // if the bucket has already been created, no need to continue
201 if (atomicState.isBucketCreated) {
205 if (!atomicState.bucketName) {
206 atomicState.bucketName = atomicState.bucketKey
208 if (!atomicState.accessKey) {
211 def bucketName = "${atomicState.bucketName}"
212 def bucketKey = "${atomicState.bucketKey}"
213 def accessKey = "${atomicState.accessKey}"
215 def bucketCreateBody = new JsonSlurper().parseText("{\"bucketKey\": \"$bucketKey\", \"bucketName\": \"$bucketName\"}")
217 def bucketCreatePost = [
218 uri: "https://${atomicState.grokerSubdomain}.initialstate.com/api/buckets",
220 "Content-Type": "application/json",
221 "X-IS-AccessKey": accessKey
223 body: bucketCreateBody
226 log.debug bucketCreatePost
229 // Create a bucket on Initial State so the data has a logical grouping
230 httpPostJson(bucketCreatePost) { resp ->
231 log.debug "bucket posted"
232 if (resp.status >= 400) {
233 log.error "bucket not created successfully"
235 atomicState.isBucketCreated = true
239 log.error "bucket creation error: $e"
244 def genericHandler(evt) {
245 log.trace "$evt.displayName($evt.name:$evt.unit) $evt.value"
247 def key = "$evt.displayName($evt.name)"
248 if (evt.unit != null) {
249 key = "$evt.displayName(${evt.name}_$evt.unit)"
251 def value = "$evt.value"
255 eventHandler(key, value)
258 def eventHandler(name, value) {
259 def epoch = now() / 1000
261 def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
265 log.debug "Shipped Event: " + event
268 def tryShipEvents(event) {
270 def grokerSubdomain = atomicState.grokerSubdomain
271 // can't ship events if there is no grokerSubdomain
272 if (grokerSubdomain == null || grokerSubdomain == "") {
273 log.error "streaming url is currently null"
276 def accessKey = atomicState.accessKey
277 def bucketKey = atomicState.bucketKey
278 // can't ship if access key and bucket key are null, so finish trying
279 if (accessKey == null || bucketKey == null) {
284 uri: "https://${grokerSubdomain}.initialstate.com/api/events",
286 "Content-Type": "application/json",
287 "X-IS-BucketKey": "${bucketKey}",
288 "X-IS-AccessKey": "${accessKey}",
289 "Accept-Version": "0.0.2"
295 // post the events to initial state
296 httpPostJson(eventPost) { resp ->
297 log.debug "shipped events and got ${resp.status}"
298 if (resp.status >= 400) {
299 log.error "shipping failed... ${resp.data}"
303 log.error "shipping events failed: $e"