Update notify-me-with-hue.groovy
[smartapps.git] / third-party / ecobeeGenerateStats.groovy
1 /**
2  *  ecobeeGenerateStats
3  *
4  *  Copyright 2015 Yves Racine
5  *  LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
6  *
7  *  Developer retains all right, title, copyright, and interest, including all copyright, patent rights, trade secret 
8  *  in the Background technology. May be subject to consulting fees under the Agreement between the Developer and the Customer. 
9  *  Developer grants a non exclusive perpetual license to use the Background technology in the Software developed for and delivered 
10  *  to Customer under this Agreement. However, the Customer shall make no commercial use of the Background technology without
11  *  Developer's written consent.
12  *
13  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
14  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
15  *
16  *  Software Distribution is restricted and shall be done only with Developer's written approval.
17  * 
18  *  N.B. Requires MyEcobee device available at 
19  *          http://www.ecomatiqhomes.com/#!store/tc3yr 
20  */
21 import java.text.SimpleDateFormat 
22 definition(
23     name: "${get_APP_NAME()}",
24     namespace: "yracine",
25     author: "Yves Racine",
26     description: "This smartapp allows a ST user to generate runtime stats (daily by scheduling or based on custom dates) on their devices controlled by ecobee such as a heating & cooling component,fan, dehumidifier/humidifier/HRV/ERV. ",
27     category: "My Apps",
28     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
29     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png"
30 )
31
32 preferences {
33         section("About") {
34                 paragraph "${get_APP_NAME()}, the smartapp that generates daily runtime reports about your ecobee components"
35                 paragraph "Version 2.5.1" 
36                 paragraph "If you like this smartapp, please support the developer via PayPal and click on the Paypal link below " 
37                         href url: "https://www.paypal.me/ecomatiqhomes",
38                                 title:"Paypal donation..."
39                 paragraph "Copyright©2014 Yves Racine"
40                         href url:"http://github.com/yracine/device-type.myecobee", style:"embedded", required:false, title:"More information..."  
41                                 description: "http://github.com/yracine/device-type.myecobee/blob/master/README.md"
42         }
43
44         section("Generate daily stats for this ecobee thermostat") {
45                 input "ecobee", "device.myEcobeeDevice", title: "Ecobee?"
46
47         }
48         section("Start date for the initial run, format = YYYY-MM-DD") {
49                 input "givenStartDate", "text", title: "Beginning Date [default=yesterday]", required: false
50         }        
51         section("Start time for initial run HH:MM (24HR)") {
52                 input "givenStartTime", "text", title: "Beginning time [default=00:00]" , required: false
53         }        
54         section("End date for the initial run = YYYY-MM-DD") {
55                 input "givenEndDate", "text", title: "End Date [default=today]", required: false
56         }        
57         section("End time for the initial run (24HR)" ) {
58                 input "givenEndTime", "text", title: "End time [default=00:00]", required: false
59         }        
60         section( "Notifications" ) {
61                 input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false, default:"No"
62                 input "phoneNumber", "phone", title: "Send a text message?", required: false    
63         }
64         section("Detailed Notifications") {
65                 input "detailedNotif", "bool", title: "Detailed Notifications?", required:false
66         }
67         section("Enable Amazon Echo/Ask Alexa Notifications [optional, default=false]") {
68                 input (name:"askAlexaFlag", title: "Ask Alexa verbal Notifications?", type:"bool",
69                         description:"optional",required:false)
70                 input (name:"listOfMQs",  type:"enum", title: "List of the Ask Alexa Message Queues (default=Primary)", options: state?.askAlexaMQ, multiple: true, required: false,
71                         description:"optional")            
72                 input "AskAlexaExpiresInDays", "number", title: "Ask Alexa's messages expiration in days (default=2 days)?", required: false
73         }
74     
75     
76 }
77
78 def installed() {
79         log.debug "Installed with settings: ${settings}"
80
81         initialize()
82 }
83
84 def updated() {
85         log.debug "Updated with settings: ${settings}"
86
87         unsubscribe()
88         unschedule()    
89         initialize()
90 }
91
92 def initialize() {
93
94         atomicState?.timestamp=''
95         atomicState?.componentAlreadyProcessed=''
96         atomicState?.retries=0    
97
98         runIn((1*60),   "generateStats") // run 1 minute later as it requires notification.     
99         subscribe(app, appTouch)
100         atomicState?.poll = [ last: 0, rescheduled: now() ]
101
102         //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
103         subscribe(location, "sunset", rescheduleIfNeeded)
104         subscribe(location, "mode", rescheduleIfNeeded)
105         subscribe(location, "sunsetTime", rescheduleIfNeeded)
106         subscribe(location, "askAlexaMQ", askAlexaMQHandler)
107         rescheduleIfNeeded()   
108 }
109
110 def askAlexaMQHandler(evt) {
111         if (!evt) return
112         switch (evt.value) {
113                 case "refresh":
114                 state?.askAlexaMQ = evt.jsonData && evt.jsonData?.queues ? evt.jsonData.queues : []
115                 log.info("askAlexaMQHandler>refresh value=$state?.askAlexaMQ")        
116                 break
117         }
118 }
119
120 def rescheduleIfNeeded(evt) {
121         if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
122         Integer delay = (24*60) // By default, do it every day
123         BigDecimal currentTime = now()    
124         BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))  
125  
126         if (lastPollTime != currentTime) {    
127                 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)      
128                 log.info "rescheduleIfNeeded>last poll was  ${lastPollTimeInMinutes.toString()} minutes ago"
129         }
130         if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
131                 log.info "rescheduleIfNeeded>scheduling dailyRun in ${delay} minutes.."
132 //              generate the stats every day at 0:10
133
134                 schedule("0 10 0 * * ?", dailyRun)    
135         }
136     
137         // Update rescheduled state
138     
139         if (!evt) atomicState?.poll["rescheduled"] = now()
140 }
141    
142
143 def appTouch(evt) {
144         atomicState?.timestamp=''
145         atomicState?.componentAlreadyProcessed=''
146         generateStats()
147 }
148
149
150
151 void dailyRun() {
152         Integer delay = (24*60) // By default, do it every day
153         atomicState?.poll["last"] = now()
154                 
155         //schedule the rescheduleIfNeeded() function
156     
157         if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
158                 log.info "takeAction>scheduling rescheduleIfNeeded() in ${delay} minutes.."
159                 schedule("0 10 0 * * ?", rescheduleIfNeeded)    
160                 // Update rescheduled state
161                 atomicState?.poll["rescheduled"] = now()
162         }
163         settings.givenStartDate=null
164         settings.givenStartTime=null
165         settings.givenEndDate=null
166         settings.givenEndTime=null
167         if (detailedNotif) {    
168                 log.debug("dailyRun>for $ecobee, about to call generateStats() with settings.givenEndDate=${settings.givenEndDate}")
169         }    
170         atomicState?.componentAlreadyProcessed=''
171         atomicState?.retries=0
172         generateStats()
173     
174 }
175
176 void reRunIfNeeded() {
177         if (detailedNotif) {    
178                 log.debug("reRunIfNeeded>About to call generateStats() with state.componentAlreadyProcessed=${atomicState?.componentAlreadyProcessed}")
179         }    
180         generateStats()
181    
182 }
183
184
185
186 private String formatISODateInLocalTime(dateInString, timezone='') {
187         def myTimezone=(timezone)?TimeZone.getTimeZone(timezone):location.timeZone 
188         if ((dateInString==null) || (dateInString.trim()=="")) {
189                 return (new Date().format("yyyy-MM-dd HH:mm:ss", myTimezone))
190         }    
191         SimpleDateFormat ISODateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
192         Date ISODate = ISODateFormat.parse(dateInString)
193         String dateInLocalTime =new Date(ISODate.getTime()).format("yyyy-MM-dd HH:mm:ss", myTimezone)
194         log.debug("formatDateInLocalTime>dateInString=$dateInString, dateInLocalTime=$dateInLocalTime")    
195         return dateInLocalTime
196 }
197
198
199 private def formatDate(dateString) {
200         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm zzz")
201         Date aDate = sdf.parse(dateString)
202         return aDate
203 }
204
205 private def get_nextComponentStats(component='') {
206         def nextInLine=[:]
207
208         def components = [
209                 '': 
210                         [position:1, next: 'auxHeat1'
211                         ], 
212                 'auxHeat1': 
213                         [position:2, next: 'auxHeat2'
214                         ], 
215                 'auxHeat2': 
216                         [position:3, next: 'auxHeat3'
217                         ], 
218                 'auxHeat3': 
219                         [position:4, next: 'compCool1'
220                         ], 
221                 'compCool1': 
222                         [position:5, next: 'compCool2'
223                         ], 
224                 'compCool2': 
225                         [position:6, next: 'humidifier'
226                         ],
227                 'humidifier': 
228                         [position:7, next: 'dehumidifier'
229                         ],
230                 'dehumidifier': 
231                         [position:8, next: 'ventilator'
232                         ],
233                 'ventilator': 
234                         [position:9, next: 'fan'
235                         ],
236                 'fan': 
237                         [position:10, next: 'done'
238                         ]
239                 ]
240         try {
241                 nextInLine = components.getAt(component)
242         } catch (any) {
243                 if (detailedNotif) {
244                         log.debug "get_nextComponentStats>${component} not found"
245                 }   
246                 nextInLine=[position:1,next:'auxHeat1']        
247         }        
248     
249         if (detailedNotif) {
250                 log.debug "get_nextComponentStats>got ${component}'s next component from components table= ${nextInLine}"
251         }
252         return nextInLine
253                             
254 }
255
256
257 void generateStats() {  
258         def MAX_POSITION=10
259         def MAX_RETRIES=4
260         float runtimeTotalYesterday,runtimeTotalDaily    
261         String dateInLocalTime = new Date().format("yyyy-MM-dd", location.timeZone) 
262         def delay = 2
263     
264         atomicState?.retries=   ((atomicState?.retries==null) ?:0) +1
265
266         try {
267                 unschedule(reRunIfNeeded)
268         } catch (e) {
269     
270                 if (detailedNotif) {    
271                         log.debug("${get_APP_NAME()}>Exception $e while unscheduling reRunIfNeeded")
272                 }       
273         }    
274     
275         if (atomicState?.retries >= MAX_RETRIES) { 
276                 if (detailedNotif) {    
277                         log.debug("${get_APP_NAME()}>Max retries reached, exiting")
278                         send("max retries reached ${atomicState?.retries}), exiting")
279                 }       
280         }       
281     
282         def component=atomicState?.componentAlreadyProcessed    // use logic to restart the batch process if needed due to ST rate limiting
283         def nextComponent  = get_nextComponentStats(component) // get next Component To Be Processed    
284         if (detailedNotif) {    
285                 log.debug("${get_APP_NAME()}>for $ecobee, about to process nextComponent=${nextComponent}, state.componentAlreadyProcessed=${atomicState?.componentAlreadyProcessed}")
286         }       
287         if (atomicState?.timestamp == dateInLocalTime && nextComponent.position >=MAX_POSITION) {
288                 return // the daily stats are already generated 
289         } else {        
290                 // schedule a rerun till the stats are generated properly
291                 schedule("0 0/${delay} * * * ?", reRunIfNeeded)
292                 
293         }       
294         
295         String timezone = new Date().format("zzz", location.timeZone)
296         String dateAtMidnight = dateInLocalTime + " 00:00 " + timezone    
297         if (detailedNotif) {    
298                 log.debug("${get_APP_NAME()}>date at Midnight= ${dateAtMidnight}")
299         }       
300         Date endDate = formatDate(dateAtMidnight) 
301         Date startDate = endDate -1
302         
303         def reportStartDate = (settings.givenStartDate) ?: startDate.format("yyyy-MM-dd", location.timeZone)
304         def reportStartTime=(settings.givenStartTime) ?:"00:00"    
305         def dateTime = reportStartDate + " " + reportStartTime + " " + timezone
306         startDate = formatDate(dateTime)
307         Date yesterday = startDate-1    
308
309         if (detailedNotif) {
310                 log.debug("${get_APP_NAME()}>start dateTime = ${dateTime}, startDate in UTC = ${startDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
311         }    
312         def reportEndDate = (settings.givenEndDate) ?: endDate.format("yyyy-MM-dd", location.timeZone) 
313         def reportEndTime=(settings.givenEndTime) ?:"00:00"    
314         dateTime = reportEndDate + " " + reportEndTime + " " + timezone
315         endDate = formatDate(dateTime)
316         if (detailedNotif) {    
317                 log.debug("${get_APP_NAME()}>end dateTime = ${dateTime}, endDate in UTC =${endDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
318         }
319     
320     
321         // Get the auxHeat1's runtime for startDate-endDate period
322         component = 'auxHeat1'
323         if (nextComponent.position <= 1) { 
324                 generateRuntimeReport(component,startDate, endDate)
325                 runtimeTotalDaily = (ecobee.currentAuxHeat1RuntimeDaily) ? ecobee.currentAuxHeat1RuntimeDaily.toFloat().round(2):0
326                 if (runtimeTotalDaily) {
327                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
328                 }     
329     
330                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for yesterday
331                 runtimeTotalYesterday = (ecobee.currentAuxHeat1RuntimeYesterday)? ecobee.currentAuxHeat1RuntimeYesterday.toFloat().round(2):0
332                 atomicState?.componentAlreadyProcessed=component
333                 if (detailedNotif && runtimeTotalYesterday) {
334                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
335                 }     
336         }     
337         
338         int heatStages = ecobee.currentHeatStages.toInteger()
339     
340         component = 'auxHeat2'
341         if (heatStages >1 && (nextComponent.position <= 2) ) { 
342     
343 //      Get the auxHeat2's runtime for startDate-endDate period
344         
345                 generateRuntimeReport(component,startDate, endDate)
346                 runtimeTotalDaily = (ecobee.currentAuxHeat2RuntimeDaily)? ecobee.currentAuxHeat2RuntimeDaily.toFloat().round(2):0
347                 if (runtimeTotalDaily) {
348                         send "On  ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
349                 }     
350                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for yesterday
351                 runtimeTotalYesterday = (ecobee.currentAuxHeat2RuntimeYesterday)? ecobee.currentAuxHeat2RuntimeYesterday.toFloat().round(2):0
352                 atomicState?.componentAlreadyProcessed=component
353                 if (detailedNotif && runtimeTotalYesterday) {
354                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
355                 }     
356         }     
357
358         component = 'auxHeat3'
359         if (heatStages >2 && nextComponent.position <= 3) { 
360     
361 //      Get the auxHeat3's runtime for startDate-endDate period
362         
363                 generateRuntimeReport(component,startDate, endDate)
364                 runtimeTotalDaily = (ecobee.currentAuxHeat3RuntimeDaily)? ecobee.currentAuxHeat3RuntimeDaily.toFloat().round(2):0
365                 if (runtimeTotalDaily) {
366                         send "On ${String.format('%tF', startDate)},${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
367                 }     
368                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for yesterday
369                 runtimeTotalYesterday = (ecobee.currentAuxHeat3RuntimeYesterday)? ecobee.currentAuxHeat3RuntimeYesterday.toFloat().round(2):0
370                 if (detailedNotif && runtimeTotalYesterday) {
371                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
372                 }     
373         }     
374
375 // Get the compCool1's runtime for startDate-endDate period
376
377         int coolStages = ecobee.currentCoolStages.toInteger()
378         component = 'compCool1'
379
380         if (nextComponent.position <= 4) {
381                 generateRuntimeReport(component,startDate, endDate)
382                 runtimeTotalDaily = (ecobee.currentCompCool1RuntimeDaily)? ecobee.currentCompCool1RuntimeDaily.toFloat().round(2):0
383                 if (runtimeTotalDaily) {
384                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
385                 }     
386                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for the day before
387                 runtimeTotalYesterday = (ecobee.currentCompCool1RuntimeYesterday)? ecobee.currentCompCool1RuntimeYesterday.toFloat().round(2):0
388                 atomicState?.componentAlreadyProcessed=component
389                 if (detailedNotif && runtimeTotalYesterday) {
390                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
391                 }     
392         }     
393         
394 //      Get the compCool2's runtime for startDate-endDate period
395
396         component = 'compCool2'
397         if (coolStages >1 && nextComponent.position <= 5) {
398                 generateRuntimeReport(component,startDate, endDate)
399                 runtimeTotalDaily = (ecobee.currentCompCool2RuntimeDaily)? ecobee.currentCompCool2RuntimeDaily.toFloat().round(2):0
400                 if (runtimeTotalDaily) {
401                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
402                 }     
403                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for the day before
404                 runtimeTotalYesterday = (ecobee.currentCompCool2RuntimeYesterday)? ecobee.currentCompCool2RuntimeYesterday.toFloat().round(2):0
405                 atomicState?.componentAlreadyProcessed=component
406                 if (detailedNotif && runtimeTotalYesterday ) {
407                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
408                 }     
409         } 
410
411         
412         def hasDehumidifier = (ecobee.currentHasDehumidifier) ? ecobee.currentHasDehumidifier : 'false' 
413         def hasHumidifier = (ecobee.currentHasHumidifier) ? ecobee.currentHasHumidifier : 'false' 
414         def hasHrv = (ecobee.currentHasHrv)? ecobee.currentHasHrv : 'false' 
415         def hasErv = (ecobee.currentHasErv)? ecobee.currentHasErv : 'false' 
416
417         component = "humidifier"
418         if (hasHumidifier=='true' && (nextComponent.position <= 6)) {
419                 // Get the humidifier's runtime for startDate-endDate period
420                 generateRuntimeReport(component,startDate, endDate)
421                 runtimeTotalDaily = (ecobee.currentHumidifierRuntimeDaily)? ecobee.currentHumidifierRuntimeDaily.toFloat().round(2):0
422                 atomicState?.componentAlreadyProcessed=component
423                 if (runtimeTotalDaily) {
424                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
425                 }     
426     
427         }
428
429         component = 'dehumidifier'
430         if (hasDehumidifier=='true' && (nextComponent.position <= 7)) {
431         // Get the dehumidifier's for startDate-endDate period
432                 generateRuntimeReport(component,startDate, endDate)
433                 runtimeTotalDaily = (ecobee.currentDehumidifierRuntimeDaily)? ecobee.currentDehumidifierRuntimeDaily.toFloat().round(2):0
434                 atomicState?.componentAlreadyProcessed=component
435                 if (runtimeTotalDaily) {
436                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
437                 }     
438
439         }
440         component = 'ventilator'
441         if (hasHrv=='true' || hasErv=='true' && (nextComponent.position <= 8)) {
442         // Get the ventilator's runtime for  startDate-endDate period
443                 generateRuntimeReport(component,startDate, endDate)
444                 runtimeTotalDaily = (ecobee.currentVentilatorRuntimeDaily)? ecobee.currentVentilatorRuntimeDaily.toFloat().round(2):0
445                 atomicState?.componentAlreadyProcessed=component
446                 if (runtimeTotalDaily) {
447                         send "On ${String.format('%tF', startDate)}, ${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
448                 }     
449
450         }
451
452         component = 'fan'
453 //      Get the fan's runtime for startDate-endDate period
454         if (nextComponent.position <= 9) {
455
456                 generateRuntimeReport(component,startDate, endDate)
457                 runtimeTotalDaily = (ecobee.currentFanRuntimeDaily)? ecobee.currentFanRuntimeDaily.toFloat().round(2):0
458                 if (runtimeTotalDaily) {
459                         send "On ${String.format('%tF', startDate)},${ecobee} ${component}'s runtime stats=${runtimeTotalDaily} minutes", settings.askAlexaFlag
460                 }     
461                 generateRuntimeReport(component,yesterday, startDate,'yesterday') // generate stats for the day before
462                 runtimeTotalYesterday = (ecobee.currentFanRuntimeYesterday)? ecobee.currentFanRuntimeYesterday.toFloat().round(2):0
463                 atomicState?.componentAlreadyProcessed=component
464                 if (detailedNotif && runtimeTotalYesterday) {
465                         send "And, on ${String.format('%tF', yesterday)}, ${ecobee} ${component}'s runtime stats for the day before=${runtimeTotalYesterday} minutes"
466                 }     
467         }     
468
469         component=atomicState?.componentAlreadyProcessed        
470         nextComponent  = get_nextComponentStats(component) // get nextComponentToBeProcessed    
471         if (nextComponent.position >= MAX_POSITION) {
472                 send "generated ${ecobee}'s daily stats done for ${String.format('%tF', startDate)} - ${String.format('%tF', endDate)} period"
473                 atomicState?.timestamp = dateInLocalTime // save the local date to avoid re-execution    
474                 unschedule(reRunIfNeeded) // No need to reschedule again as the stats are completed.
475                 atomicState?.retries=0                  
476         }
477         
478 }
479
480 void generateRuntimeReport(component, startDate, endDate, frequence='daily') {
481
482         if (detailedNotif) {
483                 log.debug("${get_APP_NAME()}>For ${ecobee} ${component}, about to call getReportData with endDate in UTC =${endDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
484         }        
485         ecobee.getReportData("", startDate, endDate, null, null, component,false)
486         if (detailedNotif) {
487                 log.debug("${get_APP_NAME()}>For ${ecobee} ${component}, about to call generateRuntimeReportEvents with endDate in UTC =${endDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
488         }        
489         ecobee.generateReportRuntimeEvents(component, startDate,endDate, 0, null,frequence)
490
491 }
492
493 private send(msg, askAlexa=false) {
494         def message = "${get_APP_NAME()}>${msg}"
495         if (sendPushMessage == "Yes") {
496                         sendPush(message)
497         }
498         if (askAlexa) {
499                 def expiresInDays=(AskAlexaExpiresInDays)?:2    
500                 sendLocationEvent(
501                         name: "AskAlexaMsgQueue", 
502                         value: "${get_APP_NAME()}", 
503                         isStateChange: true, 
504                         descriptionText: msg, 
505                         data:[
506                                 queues: listOfMQs,
507                                 expires: (expiresInDays*24*60*60)  /* Expires after 2 days by default */
508                         ]
509                 )
510         } /* End if Ask Alexa notifications*/
511
512         if (phone) {
513                 log.debug("sending text message")
514                 sendSms(phone, message)
515         }
516     
517         log.debug msg
518     
519 }
520
521 private def get_APP_NAME() {
522         return "ecobeeGenerateStats"
523 }