Update bright-when-dark-and-or-bright-after-sunset.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 "automatic", "capability.presenceSensor", title: "Automatic Connected Device(s)", required: false, multiple: true
50                 input "detectors", "capability.smokeDetector", title: "Smoke/CarbonMonoxide Detectors", required: false, multiple: true
51                 input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity sensors", required: false, multiple: true
52                 input "waters", "capability.waterSensor", title: "Water sensors", required: false, multiple: true
53                 input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance sensor", required: false, multiple: true
54                 input "locks", "capability.lock", title: "Locks", required: false, multiple: true
55                 input "contacts", "capability.contactSensor", title: "Doors open/close", required: false, multiple: true
56                 input "accelerations", "capability.accelerationSensor", title: "Accelerations", required: false, multiple: true
57                 input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
58                 input "presence", "capability.presenceSensor", title: "Presence", required: false, multiple: true
59                 input "switches", "capability.switch", title: "Switches", required: false, multiple: true
60                 input "dimmerSwitches", "capability.switchLevel", title: "Dimmer Switches", required: false, multiple: true
61                 input "batteries", "capability.battery", title: "Battery-powered devices", required: false, multiple: true
62                 input "powers", "capability.powerMeter", title: "Power Meters", required: false, multiple: true
63                 input "energys", "capability.energyMeter", title: "Energy Meters", required: false, multiple: true
64
65         }
66
67         section("GroveStreams Feed PUT API key...") {
68                 input "channelKey", "text", title: "API key"
69         }
70         section("Sending data at which interval in minutes (default=5)?") {
71                 input "givenInterval", "number", title: 'Send Data Interval', required: false
72         }
73 }
74
75 def installed() {
76         initialize()
77 }
78
79 def updated() {
80         unsubscribe()
81         unschedule()
82         initialize()
83 }
84
85 def initialize() {
86         subscribe(temperatures, "temperature", handleTemperatureEvent)
87         subscribe(humidities, "humidity", handleHumidityEvent)
88         subscribe(waters, "water", handleWaterEvent)
89         subscribe(waters, "water", handleWaterEvent)
90         subscribe(detectors, "smoke", handleSmokeEvent)
91         subscribe(detectors, "carbonMonoxide", handleCarbonMonoxideEvent)
92         subscribe(illuminances, "illuminance", handleIlluminanceEvent)
93         subscribe(contacts, "contact", handleContactEvent)
94         subscribe(locks, "lock", handleLockEvent)
95         subscribe(accelerations, "acceleration", handleAccelerationEvent)
96         subscribe(motions, "motion", handleMotionEvent)
97         subscribe(presence, "presence", handlePresenceEvent)
98         subscribe(switches, "switch", handleSwitchEvent)
99         subscribe(dimmerSwitches, "switch", handleSwitchEvent)
100         subscribe(dimmerSwitches, "level", handleSetLevelEvent)
101         subscribe(batteries, "battery", handleBatteryEvent)
102         subscribe(powers, "power", handlePowerEvent)
103         subscribe(energys, "energy", handleEnergyEvent)
104         subscribe(thermostats, "heatingSetpoint", handleHeatingSetpointEvent)
105         subscribe(thermostats, "coolingSetpoint", handleCoolingSetpointEvent)
106         subscribe(thermostats, "thermostatMode", handleThermostatModeEvent)
107         subscribe(thermostats, "fanMode", handleFanModeEvent)
108         subscribe(thermostats, "thermostatOperatingState", handleThermostatOperatingStateEvent)
109         subscribe(automatic, "presence",handleDailyStats)
110         
111         def queue = []
112         atomicState.queue=queue
113     
114         if (atomicState.queue==null) {
115                 atomicState.queue = []
116         }    
117         atomicState?.poll = [ last: 0, rescheduled: now() ]
118
119         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
120         log.debug "initialize>scheduling processQueue every ${delay} minutes"
121
122         //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
123         subscribe(location, "sunrise", rescheduleIfNeeded)
124         subscribe(location, "sunset", rescheduleIfNeeded)
125         subscribe(location, "mode", rescheduleIfNeeded)
126         subscribe(location, "sunriseTime", rescheduleIfNeeded)
127         subscribe(location, "sunsetTime", rescheduleIfNeeded)
128         subscribe(app, appTouch)
129
130         //rescheduleIfNeeded()   
131 }
132
133 def appTouch(evt) {
134         rescheduleIfNeeded(evt)
135         processQueue()
136         def queue = []
137         atomicState.queue=queue
138 }
139
140
141 def rescheduleIfNeeded(evt) {
142         if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
143         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
144         BigDecimal currentTime = now()    
145         BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))  
146         if (lastPollTime != currentTime) {    
147                 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)      
148                 log.info "rescheduleIfNeeded>last poll was  ${lastPollTimeInMinutes.toString()} minutes ago"
149         }
150         if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
151                 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
152                 unschedule()     
153                 //schedule("14:00", processQueue)
154         }
155         // Update rescheduled state
156     
157         if (!evt) {
158                 atomicState.poll["rescheduled"] = now()    
159         }        
160 }    
161
162 def handleTemperatureEvent(evt) {
163         queueValue(evt) {
164                 it.toString()
165         }
166 }
167
168 def handleHumidityEvent(evt) {
169         queueValue(evt) {
170                 it.toString()
171         }
172 }
173
174 def handleHeatingSetpointEvent(evt) {
175         queueValue(evt) {
176                 it.toString()
177         }
178 }
179 def handleCoolingSetpointEvent(evt) {
180         queueValue(evt) {
181                 it.toString()
182         }
183 }
184
185 def handleThermostatModeEvent(evt) {
186         queueValue(evt) {
187                 it.toString()
188         }
189 }
190 def handleFanModeEvent(evt) {
191         queueValue(evt) {
192                 it.toString()
193         }
194 }
195 def handleHumidifierModeEvent(evt) {
196         queueValue(evt) {
197                 it.toString()
198         }
199 }
200 def handleHumidifierLevelEvent(evt) {
201         queueValue(evt) {
202                 it.toString()
203         }
204 }
205 def handleDehumidifierModeEvent(evt) {
206         queueValue(evt) {
207                 it.toString()
208         }
209 }
210 def handleDehumidifierLevelEvent(evt) {
211         queueValue(evt) {
212                 it.toString()
213         }
214 }
215 def handleVentilatorModeEvent(evt) {
216         queueValue(evt) {
217                 it.toString()
218         }
219 }
220 def handleFanMinOnTimeEvent(evt) {
221         queueValue(evt) {
222                 it.toString()
223         }
224 }
225 def handleVentilatorMinOnTimeEvent(evt) {
226         queueValue(evt) {
227                 it.toString()
228         }
229 }
230
231 def handleThermostatOperatingStateEvent(evt) {
232         queueValue(evt) {
233                 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
234         }
235
236 }
237 def handleDailyStats(evt) {
238         queueValue(evt) {
239                 it.toString()
240         }
241
242 }
243 def handleEquipmentStatusEvent(evt) {
244         queueValue(evt) {
245                 it.toString()
246         }
247 }
248
249 def handleProgramNameEvent(evt) {
250         queueValue(evt) {
251                 it.toString()
252         }
253 }
254
255 def handleWaterEvent(evt) {
256         queueValue(evt) {
257                 it.toString()
258         }
259 }
260 def handleSmokeEvent(evt) {
261         queueValue(evt) {
262                 it.toString()
263         }
264 }
265 def handleCarbonMonoxideEvent(evt) {
266         queueValue(evt) {
267                 it.toString()
268         }
269 }
270
271 def handleIlluminanceEvent(evt) {
272         log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
273         queueValue(evt) {
274                 it.toString()
275         }
276 }
277
278 def handleLockEvent(evt) {
279         queueValue(evt) {
280                 it == "locked" ? 1 : 0
281         }
282 }
283
284 def handleBatteryEvent(evt) {
285         queueValue(evt) {
286                 it.toString()
287         }
288 }
289
290 def handleContactEvent(evt) {
291         queueValue(evt) {
292                 it == "open" ? 1 : 0
293         }
294 }
295
296 def handleAccelerationEvent(evt) {
297         queueValue(evt) {
298                 it == "active" ? 1 : 0
299         }
300 }
301
302 def handleMotionEvent(evt) {
303         queueValue(evt) {
304                 it == "active" ? 1 : 0
305         }
306 }
307
308 def handlePresenceEvent(evt) {
309         queueValue(evt) {
310                 it == "present" ? 1 : 0
311         }
312 }
313
314 def handleSwitchEvent(evt) {
315         queueValue(evt) {
316                 it == "on" ? 1 : 0
317         }
318 }
319
320 def handleSetLevelEvent(evt) {
321         queueValue(evt) {
322                 it.toString()
323         }
324 }
325
326 def handlePowerEvent(evt) {
327         if (evt.value) {
328                 queueValue(evt) {
329                         it.toString()
330                 }
331         }
332 }
333
334 def handleEnergyEvent(evt) {
335         if (evt.value) {
336                 queueValue(evt) {
337                         it.toString()
338                 }
339         }
340 }
341 def handleCostEvent(evt) {
342         if (evt.value) {
343                 queueValue(evt) {
344                         it.toString()
345                 }
346         }
347 }
348
349 private queueValue(evt, Closure convert) {
350         def MAX_QUEUE_SIZE=95000
351         def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
352         def queue
353
354         queue = atomicState.queue
355         queue << jsonPayload
356         atomicState.queue = queue    
357         def queue_size = queue.toString().length()
358         def last_item_in_queue = queue[queue.size() -1]    
359         log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
360         if (queue_size >  MAX_QUEUE_SIZE) {
361                 processQueue()
362         }
363 }
364
365 def processQueue() {
366         Integer delay  = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
367         atomicState?.poll["last"] = now()
368
369         if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
370                 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
371                 //schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
372                 // Update rescheduled state
373                 atomicState?.poll["rescheduled"] = now()
374         }
375
376         def queue = atomicState.queue
377     
378    
379         def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
380         log.debug "processQueue"
381         if (queue != []) {
382                 log.debug "Events to be sent to groveStreams: ${queue}"
383
384                 /*try {
385                         httpPutJson([uri: url, body: queue]) {response ->
386                                 if (response.status != 200) {
387                                         log.debug "GroveStreams logging failed, status = ${response.status}"
388                                 } else {
389                                         log.debug "GroveStreams accepted event(s)"
390                                         // reset the queue 
391                                         queue =[]                         
392                                         atomicState.queue = queue                     
393                                 }
394                         }
395                 } catch (groovyx.net.http.ResponseParseException e) {
396                         // ignore error 200, bogus exception
397                         if (e.statusCode != 200) {
398                                 log.error "Grovestreams: ${e}"
399                         } else {
400                                 log.debug "GroveStreams accepted event(s)"
401                         }
402                         // reset the queue 
403                         queue =[]                         
404                         atomicState.queue = queue                      
405             
406                 } catch (e) {
407                         def errorInfo = "Error sending value: ${e}"
408                         log.error errorInfo
409                         // reset the queue 
410                         queue =[]                         
411                         atomicState.queue = queue                        
412                 }*/
413         }
414
415 }