Checking in all the SmartThings apps; both official and third-party.
[smartapps.git] / third-party / groveStreams.groovy
1 /**
2  *  groveStreams
3  *
4  *  Copyright 2014 Yves Racine
5  *
6  *  LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
7  *
8  *  Developer retains all right, title, copyright, and interest, including all copyright, patent rights, trade secret 
9  *  in the Background technology. May be subject to consulting fees under the Agreement between the Developer and the Customer. 
10  *  Developer grants a non exclusive perpetual license to use the Background technology in the Software developed for and delivered 
11  *  to Customer under this Agreement. However, the Customer shall make no commercial use of the Background technology without
12  *  Developer's written consent.
13  *
14  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
15  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
16  *  Software Distribution is restricted and shall be done only with Developer's written approval.
17  *
18  *  Based on code from Jason Steele & Minollo
19  *  Adapted to be compatible with MyEcobee and My Automatic devices which are available at my store:
20  *          http://www.ecomatiqhomes.com/#!store/tc3yr 
21  * 
22  */
23 definition(
24         name: "groveStreams",
25         namespace: "yracine",
26         author: "Yves Racine",
27         description: "Log to groveStreams and send data streams based on devices selection",
28         category: "My Apps",
29         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
30         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png"
31 )
32
33
34
35 preferences {
36         section("About") {
37                 paragraph "groveStreams, the smartapp that sends your device states to groveStreams for data correlation"
38                 paragraph "Version 2.2.2" 
39                 paragraph "If you like this smartapp, please support the developer via PayPal and click on the Paypal link below " 
40                         href url: "https://www.paypal.me/ecomatiqhomes",
41                                 title:"Paypal donation..."
42                 paragraph "Copyright©2014 Yves Racine"
43                         href url:"http://github.com/yracine/device-type.myecobee", style:"embedded", required:false, title:"More information..."  
44                                 description: "http://github.com/yracine"
45         }
46         section("Log devices...") {
47                 input "temperatures", "capability.temperatureMeasurement", title: "Temperatures", required: false, multiple: true
48                 input "thermostats", "capability.thermostat", title: "Thermostats", required: false, multiple: true
49                 input "ecobees", "device.myEcobeeDevice", title: "Ecobees", required: false, multiple: true
50                 input "automatic", "capability.presenceSensor", title: "Automatic Connected Device(s)", required: false, multiple: true
51                 input "detectors", "capability.smokeDetector", title: "Smoke/CarbonMonoxide Detectors", required: false, multiple: true
52                 input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity sensors", required: false, multiple: true
53                 input "waters", "capability.waterSensor", title: "Water sensors", required: false, multiple: true
54                 input "illuminances", "capability.IlluminanceMeasurement", title: "Illuminance sensor", required: false, multiple: true
55                 input "locks", "capability.lock", title: "Locks", required: false, multiple: true
56                 input "contacts", "capability.contactSensor", title: "Doors open/close", required: false, multiple: true
57                 input "accelerations", "capability.accelerationSensor", title: "Accelerations", required: false, multiple: true
58                 input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
59                 input "presence", "capability.presenceSensor", title: "Presence", required: false, multiple: true
60                 input "switches", "capability.switch", title: "Switches", required: false, multiple: true
61                 input "dimmerSwitches", "capability.switchLevel", title: "Dimmer Switches", required: false, multiple: true
62                 input "batteries", "capability.battery", title: "Battery-powered devices", required: false, multiple: true
63                 input "powers", "capability.powerMeter", title: "Power Meters", required: false, multiple: true
64                 input "energys", "capability.energyMeter", title: "Energy Meters", required: false, multiple: true
65
66         }
67
68         section("GroveStreams Feed PUT API key...") {
69                 input "channelKey", "text", title: "API key"
70         }
71         section("Sending data at which interval in minutes (default=5)?") {
72                 input "givenInterval", "number", title: 'Send Data Interval', required: false
73         }
74 }
75
76 def installed() {
77         initialize()
78 }
79
80 def updated() {
81         unsubscribe()
82         unschedule()
83         initialize()
84 }
85
86 def initialize() {
87         subscribe(temperatures, "temperature", handleTemperatureEvent)
88         subscribe(humidities, "humidity", handleHumidityEvent)
89         subscribe(waters, "water", handleWaterEvent)
90         subscribe(waters, "water", handleWaterEvent)
91         subscribe(detectors, "smoke", handleSmokeEvent)
92         subscribe(detectors, "carbonMonoxide", handleCarbonMonoxideEvent)
93         subscribe(illuminances, "illuminance", handleIlluminanceEvent)
94         subscribe(contacts, "contact", handleContactEvent)
95         subscribe(locks, "lock", handleLockEvent)
96         subscribe(accelerations, "acceleration", handleAccelerationEvent)
97         subscribe(motions, "motion", handleMotionEvent)
98         subscribe(presence, "presence", handlePresenceEvent)
99         subscribe(switches, "switch", handleSwitchEvent)
100         subscribe(dimmerSwitches, "switch", handleSwitchEvent)
101         subscribe(dimmerSwitches, "level", handleSetLevelEvent)
102         subscribe(batteries, "battery", handleBatteryEvent)
103         subscribe(powers, "power", handlePowerEvent)
104         subscribe(energys, "energy", handleEnergyEvent)
105         subscribe(energys, "cost", handleCostEvent)
106         subscribe(thermostats, "heatingSetpoint", handleHeatingSetpointEvent)
107         subscribe(thermostats, "coolingSetpoint", handleCoolingSetpointEvent)
108         subscribe(thermostats, "thermostatMode", handleThermostatModeEvent)
109         subscribe(thermostats, "fanMode", handleFanModeEvent)
110         subscribe(thermostats, "thermostatOperatingState", handleThermostatOperatingStateEvent)
111         subscribe(ecobees, "dehumidifierMode", handleDehumidifierModeEvent)
112         subscribe(ecobees, "equipmentStatus", handleEquipmentStatusEvent)
113         subscribe(ecobees, "dehumidifierLevel", handleDehumidifierLevelEvent)
114         subscribe(ecobees, "humidifierMode", handleHumidifierModeEvent)
115         subscribe(ecobees, "humidifierLevel", handleHumidifierLevelEvent)
116         subscribe(ecobees, "fanMinOnTime", handleFanMinOnTimeEvent)
117         subscribe(ecobees, "ventilatorMode", handleVentilatorModeEvent)
118         subscribe(ecobees, "ventilatorMinOnTime", handleVentilatorMinOnTimeEvent)
119         subscribe(ecobees, "programScheduleName", handleProgramNameEvent)
120         subscribe(ecobees, "auxHeat1RuntimeDaily", handleDailyStats)
121         subscribe(ecobees, "auxHeat2RuntimeDaily", handleDailyStats)
122         subscribe(ecobees, "auxHeat3RuntimeDaily", handleDailyStats)
123         subscribe(ecobees, "compCool1RuntimeDaily", handleDailyStats)
124         subscribe(ecobees, "compCool2RuntimeDaily", handleDailyStats)
125         subscribe(ecobees, "fanRuntimeDaily", handleDailyStats)
126         subscribe(ecobees, "humidifierRuntimeDaily", handleDailyStats)
127         subscribe(ecobees, "dehumidifierRuntimeDaily", handleDailyStats)
128         subscribe(ecobees, "ventilatorRuntimeDaily", handleDailyStats)
129         subscribe(ecobees, "presence", handlePresenceEvent)
130         subscribe(ecobees, "compCool2RuntimeDaily", handleDailyStats)
131         subscribe(automatic, "yesterdayTripsAvgAverageKmpl",handleDailyStats)
132         subscribe(automatic, "yesterdayTripsAvgDistanceM",handleDailyStats)
133         subscribe(automatic, "yesterdayTripsAvgDurationS",handleDailyStats)
134         subscribe(automatic, "yesterdayTotalDistanceM",handleDailyStats)
135         subscribe(automatic, "yesterdayTripsAvgFuelVolumeL",handleDailyStats)
136         subscribe(automatic, "yesterdayTotalFuelVolumeL",handleDailyStats)
137         subscribe(automatic, "yesterdayTotalDurationS:",handleDailyStats)
138         subscribe(automatic, "yesterdayTotalNbTrips",handleDailyStats)
139         subscribe(automatic, "yesterdayTotalHardAccels",handleDailyStats)
140         subscribe(automatic, "yesterdayTotalHardBrakes:",handleDailyStats)
141         subscribe(automatic, "yesterdayTripsAvgScoreSpeeding",handleDailyStats)
142         subscribe(automatic, "yesterdayTripsAvgScoreEvents",handleDailyStats)
143         def queue = []
144         atomicState.queue=queue
145     
146         if (atomicState.queue==null) {
147                 atomicState.queue = []
148         }    
149         atomicState?.poll = [ last: 0, rescheduled: now() ]
150
151         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
152         log.debug "initialize>scheduling processQueue every ${delay} minutes"
153
154         //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
155         subscribe(location, "sunrise", rescheduleIfNeeded)
156         subscribe(location, "sunset", rescheduleIfNeeded)
157         subscribe(location, "mode", rescheduleIfNeeded)
158         subscribe(location, "sunriseTime", rescheduleIfNeeded)
159         subscribe(location, "sunsetTime", rescheduleIfNeeded)
160         subscribe(app, appTouch)
161
162         rescheduleIfNeeded()   
163 }
164
165 def appTouch(evt) {
166         rescheduleIfNeeded()
167         processQueue()
168         def queue = []
169         atomicState.queue=queue
170 }
171
172
173 def rescheduleIfNeeded(evt) {
174         if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
175         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
176         BigDecimal currentTime = now()    
177         BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))  
178         if (lastPollTime != currentTime) {    
179                 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)      
180                 log.info "rescheduleIfNeeded>last poll was  ${lastPollTimeInMinutes.toString()} minutes ago"
181         }
182         if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
183                 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
184                 unschedule        
185                 schedule("0 0/${delay} * * * ?", processQueue)
186         }
187         // Update rescheduled state
188     
189         if (!evt) {
190                 atomicState.poll["rescheduled"] = now()    
191         }        
192 }    
193
194 def handleTemperatureEvent(evt) {
195         queueValue(evt) {
196                 it.toString()
197         }
198 }
199
200 def handleHumidityEvent(evt) {
201         queueValue(evt) {
202                 it.toString()
203         }
204 }
205
206 def handleHeatingSetpointEvent(evt) {
207         queueValue(evt) {
208                 it.toString()
209         }
210 }
211 def handleCoolingSetpointEvent(evt) {
212         queueValue(evt) {
213                 it.toString()
214         }
215 }
216
217 def handleThermostatModeEvent(evt) {
218         queueValue(evt) {
219                 it.toString()
220         }
221 }
222 def handleFanModeEvent(evt) {
223         queueValue(evt) {
224                 it.toString()
225         }
226 }
227 def handleHumidifierModeEvent(evt) {
228         queueValue(evt) {
229                 it.toString()
230         }
231 }
232 def handleHumidifierLevelEvent(evt) {
233         queueValue(evt) {
234                 it.toString()
235         }
236 }
237 def handleDehumidifierModeEvent(evt) {
238         queueValue(evt) {
239                 it.toString()
240         }
241 }
242 def handleDehumidifierLevelEvent(evt) {
243         queueValue(evt) {
244                 it.toString()
245         }
246 }
247 def handleVentilatorModeEvent(evt) {
248         queueValue(evt) {
249                 it.toString()
250         }
251 }
252 def handleFanMinOnTimeEvent(evt) {
253         queueValue(evt) {
254                 it.toString()
255         }
256 }
257 def handleVentilatorMinOnTimeEvent(evt) {
258         queueValue(evt) {
259                 it.toString()
260         }
261 }
262
263 def handleThermostatOperatingStateEvent(evt) {
264         queueValue(evt) {
265                 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
266         }
267
268 }
269 def handleDailyStats(evt) {
270         queueValue(evt) {
271                 it.toString()
272         }
273
274 }
275 def handleEquipmentStatusEvent(evt) {
276         queueValue(evt) {
277                 it.toString()
278         }
279 }
280
281 def handleProgramNameEvent(evt) {
282         queueValue(evt) {
283                 it.toString()
284         }
285 }
286
287 def handleWaterEvent(evt) {
288         queueValue(evt) {
289                 it.toString()
290         }
291 }
292 def handleSmokeEvent(evt) {
293         queueValue(evt) {
294                 it.toString()
295         }
296 }
297 def handleCarbonMonoxideEvent(evt) {
298         queueValue(evt) {
299                 it.toString()
300         }
301 }
302
303 def handleIlluminanceEvent(evt) {
304         log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
305         queueValue(evt) {
306                 it.toString()
307         }
308 }
309
310 def handleLockEvent(evt) {
311         queueValue(evt) {
312                 it == "locked" ? 1 : 0
313         }
314 }
315
316 def handleBatteryEvent(evt) {
317         queueValue(evt) {
318                 it.toString()
319         }
320 }
321
322 def handleContactEvent(evt) {
323         queueValue(evt) {
324                 it == "open" ? 1 : 0
325         }
326 }
327
328 def handleAccelerationEvent(evt) {
329         queueValue(evt) {
330                 it == "active" ? 1 : 0
331         }
332 }
333
334 def handleMotionEvent(evt) {
335         queueValue(evt) {
336                 it == "active" ? 1 : 0
337         }
338 }
339
340 def handlePresenceEvent(evt) {
341         queueValue(evt) {
342                 it == "present" ? 1 : 0
343         }
344 }
345
346 def handleSwitchEvent(evt) {
347         queueValue(evt) {
348                 it == "on" ? 1 : 0
349         }
350 }
351
352 def handleSetLevelEvent(evt) {
353         queueValue(evt) {
354                 it.toString()
355         }
356 }
357
358 def handlePowerEvent(evt) {
359         if (evt.value) {
360                 queueValue(evt) {
361                         it.toString()
362                 }
363         }
364 }
365
366 def handleEnergyEvent(evt) {
367         if (evt.value) {
368                 queueValue(evt) {
369                         it.toString()
370                 }
371         }
372 }
373 def handleCostEvent(evt) {
374         if (evt.value) {
375                 queueValue(evt) {
376                         it.toString()
377                 }
378         }
379 }
380
381 private queueValue(evt, Closure convert) {
382         def MAX_QUEUE_SIZE=95000
383         def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
384         def queue
385
386         queue = atomicState.queue
387         queue << jsonPayload
388         atomicState.queue = queue    
389         def queue_size = queue.toString().length()
390         def last_item_in_queue = queue[queue.size() -1]    
391         log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
392         if (queue_size >  MAX_QUEUE_SIZE) {
393                 processQueue()
394         }
395 }
396
397 def processQueue() {
398         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
399         atomicState?.poll["last"] = now()
400
401         if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
402                 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
403                 schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
404                 // Update rescheduled state
405                 atomicState?.poll["rescheduled"] = now()
406         }
407
408         def queue = atomicState.queue
409     
410    
411         def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
412         log.debug "processQueue"
413         if (queue != []) {
414                 log.debug "Events to be sent to groveStreams: ${queue}"
415
416                 try {
417                         httpPutJson([uri: url, body: queue]) {response ->
418                                 if (response.status != 200) {
419                                         log.debug "GroveStreams logging failed, status = ${response.status}"
420                                 } else {
421                                         log.debug "GroveStreams accepted event(s)"
422                                         // reset the queue 
423                                         queue =[]                         
424                                         atomicState.queue = queue                     
425                                 }
426                         }
427                 } catch (groovyx.net.http.ResponseParseException e) {
428                         // ignore error 200, bogus exception
429                         if (e.statusCode != 200) {
430                                 log.error "Grovestreams: ${e}"
431                         } else {
432                                 log.debug "GroveStreams accepted event(s)"
433                         }
434                         // reset the queue 
435                         queue =[]                         
436                         atomicState.queue = queue                      
437             
438                 } catch (e) {
439                         def errorInfo = "Error sending value: ${e}"
440                         log.error errorInfo
441                         // reset the queue 
442                         queue =[]                         
443                         atomicState.queue = queue                        
444                 }
445         }
446
447 }