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