27398951e37885bd1a2038dc3c38cfcfab528782
[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(thermostats, "heatingSetpoint", handleHeatingSetpointEvent)
106         subscribe(thermostats, "coolingSetpoint", handleCoolingSetpointEvent)
107         subscribe(thermostats, "thermostatMode", handleThermostatModeEvent)
108         subscribe(thermostats, "fanMode", handleFanModeEvent)
109         subscribe(thermostats, "thermostatOperatingState", handleThermostatOperatingStateEvent)
110         subscribe(automatic, "presence",handleDailyStats)
111         //subscribe(automatic, "yesterdayTripsAvgDistanceM",handleDailyStats)
112         //subscribe(automatic, "yesterdayTripsAvgDurationS",handleDailyStats)
113         //subscribe(automatic, "yesterdayTotalDistanceM",handleDailyStats)
114         //subscribe(automatic, "yesterdayTripsAvgFuelVolumeL",handleDailyStats)
115         //subscribe(automatic, "yesterdayTotalFuelVolumeL",handleDailyStats)
116         //subscribe(automatic, "yesterdayTotalDurationS:",handleDailyStats)
117         //subscribe(automatic, "yesterdayTotalNbTrips",handleDailyStats)
118         //subscribe(automatic, "yesterdayTotalHardAccels",handleDailyStats)
119         //subscribe(automatic, "yesterdayTotalHardBrakes:",handleDailyStats)
120         //subscribe(automatic, "yesterdayTripsAvgScoreSpeeding",handleDailyStats)
121         //subscribe(automatic, "yesterdayTripsAvgScoreEvents",handleDailyStats)
122         def queue = []
123         atomicState.queue=queue
124     
125         if (atomicState.queue==null) {
126                 atomicState.queue = []
127         }    
128         atomicState?.poll = [ last: 0, rescheduled: now() ]
129
130         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
131         log.debug "initialize>scheduling processQueue every ${delay} minutes"
132
133         //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
134         subscribe(location, "sunrise", rescheduleIfNeeded)
135         subscribe(location, "sunset", rescheduleIfNeeded)
136         subscribe(location, "mode", rescheduleIfNeeded)
137         subscribe(location, "sunriseTime", rescheduleIfNeeded)
138         subscribe(location, "sunsetTime", rescheduleIfNeeded)
139         subscribe(app, appTouch)
140
141         //rescheduleIfNeeded()   
142 }
143
144 def appTouch(evt) {
145         rescheduleIfNeeded(evt)
146         processQueue()
147         def queue = []
148         atomicState.queue=queue
149 }
150
151
152 def rescheduleIfNeeded(evt) {
153         if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
154         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
155         BigDecimal currentTime = now()    
156         BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))  
157         if (lastPollTime != currentTime) {    
158                 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)      
159                 log.info "rescheduleIfNeeded>last poll was  ${lastPollTimeInMinutes.toString()} minutes ago"
160         }
161         if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
162                 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
163                 unschedule()     
164                 //schedule("14:00", processQueue)
165         }
166         // Update rescheduled state
167     
168         if (!evt) {
169                 atomicState.poll["rescheduled"] = now()    
170         }        
171 }    
172
173 def handleTemperatureEvent(evt) {
174         queueValue(evt) {
175                 it.toString()
176         }
177 }
178
179 def handleHumidityEvent(evt) {
180         queueValue(evt) {
181                 it.toString()
182         }
183 }
184
185 def handleHeatingSetpointEvent(evt) {
186         queueValue(evt) {
187                 it.toString()
188         }
189 }
190 def handleCoolingSetpointEvent(evt) {
191         queueValue(evt) {
192                 it.toString()
193         }
194 }
195
196 def handleThermostatModeEvent(evt) {
197         queueValue(evt) {
198                 it.toString()
199         }
200 }
201 def handleFanModeEvent(evt) {
202         queueValue(evt) {
203                 it.toString()
204         }
205 }
206 def handleHumidifierModeEvent(evt) {
207         queueValue(evt) {
208                 it.toString()
209         }
210 }
211 def handleHumidifierLevelEvent(evt) {
212         queueValue(evt) {
213                 it.toString()
214         }
215 }
216 def handleDehumidifierModeEvent(evt) {
217         queueValue(evt) {
218                 it.toString()
219         }
220 }
221 def handleDehumidifierLevelEvent(evt) {
222         queueValue(evt) {
223                 it.toString()
224         }
225 }
226 def handleVentilatorModeEvent(evt) {
227         queueValue(evt) {
228                 it.toString()
229         }
230 }
231 def handleFanMinOnTimeEvent(evt) {
232         queueValue(evt) {
233                 it.toString()
234         }
235 }
236 def handleVentilatorMinOnTimeEvent(evt) {
237         queueValue(evt) {
238                 it.toString()
239         }
240 }
241
242 def handleThermostatOperatingStateEvent(evt) {
243         queueValue(evt) {
244                 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
245         }
246
247 }
248 def handleDailyStats(evt) {
249         queueValue(evt) {
250                 it.toString()
251         }
252
253 }
254 def handleEquipmentStatusEvent(evt) {
255         queueValue(evt) {
256                 it.toString()
257         }
258 }
259
260 def handleProgramNameEvent(evt) {
261         queueValue(evt) {
262                 it.toString()
263         }
264 }
265
266 def handleWaterEvent(evt) {
267         queueValue(evt) {
268                 it.toString()
269         }
270 }
271 def handleSmokeEvent(evt) {
272         queueValue(evt) {
273                 it.toString()
274         }
275 }
276 def handleCarbonMonoxideEvent(evt) {
277         queueValue(evt) {
278                 it.toString()
279         }
280 }
281
282 def handleIlluminanceEvent(evt) {
283         log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
284         queueValue(evt) {
285                 it.toString()
286         }
287 }
288
289 def handleLockEvent(evt) {
290         queueValue(evt) {
291                 it == "locked" ? 1 : 0
292         }
293 }
294
295 def handleBatteryEvent(evt) {
296         queueValue(evt) {
297                 it.toString()
298         }
299 }
300
301 def handleContactEvent(evt) {
302         queueValue(evt) {
303                 it == "open" ? 1 : 0
304         }
305 }
306
307 def handleAccelerationEvent(evt) {
308         queueValue(evt) {
309                 it == "active" ? 1 : 0
310         }
311 }
312
313 def handleMotionEvent(evt) {
314         queueValue(evt) {
315                 it == "active" ? 1 : 0
316         }
317 }
318
319 def handlePresenceEvent(evt) {
320         queueValue(evt) {
321                 it == "present" ? 1 : 0
322         }
323 }
324
325 def handleSwitchEvent(evt) {
326         queueValue(evt) {
327                 it == "on" ? 1 : 0
328         }
329 }
330
331 def handleSetLevelEvent(evt) {
332         queueValue(evt) {
333                 it.toString()
334         }
335 }
336
337 def handlePowerEvent(evt) {
338         if (evt.value) {
339                 queueValue(evt) {
340                         it.toString()
341                 }
342         }
343 }
344
345 def handleEnergyEvent(evt) {
346         if (evt.value) {
347                 queueValue(evt) {
348                         it.toString()
349                 }
350         }
351 }
352 def handleCostEvent(evt) {
353         if (evt.value) {
354                 queueValue(evt) {
355                         it.toString()
356                 }
357         }
358 }
359
360 private queueValue(evt, Closure convert) {
361         def MAX_QUEUE_SIZE=95000
362         def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
363         def queue
364
365         queue = atomicState.queue
366         queue << jsonPayload
367         atomicState.queue = queue    
368         def queue_size = queue.toString().length()
369         def last_item_in_queue = queue[queue.size() -1]    
370         log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
371         if (queue_size >  MAX_QUEUE_SIZE) {
372                 processQueue()
373         }
374 }
375
376 def processQueue() {
377         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
378         atomicState?.poll["last"] = now()
379
380         if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
381                 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
382                 //schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
383                 // Update rescheduled state
384                 atomicState?.poll["rescheduled"] = now()
385         }
386
387         def queue = atomicState.queue
388     
389    
390         def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
391         log.debug "processQueue"
392         if (queue != []) {
393                 log.debug "Events to be sent to groveStreams: ${queue}"
394
395                 /*try {
396                         httpPutJson([uri: url, body: queue]) {response ->
397                                 if (response.status != 200) {
398                                         log.debug "GroveStreams logging failed, status = ${response.status}"
399                                 } else {
400                                         log.debug "GroveStreams accepted event(s)"
401                                         // reset the queue 
402                                         queue =[]                         
403                                         atomicState.queue = queue                     
404                                 }
405                         }
406                 } catch (groovyx.net.http.ResponseParseException e) {
407                         // ignore error 200, bogus exception
408                         if (e.statusCode != 200) {
409                                 log.error "Grovestreams: ${e}"
410                         } else {
411                                 log.debug "GroveStreams accepted event(s)"
412                         }
413                         // reset the queue 
414                         queue =[]                         
415                         atomicState.queue = queue                      
416             
417                 } catch (e) {
418                         def errorInfo = "Error sending value: ${e}"
419                         log.error errorInfo
420                         // reset the queue 
421                         queue =[]                         
422                         atomicState.queue = queue                        
423                 }*/
424         }
425
426 }