4 * Copyright 2015 Yves Racine
5 * LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
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.
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.
16 * Software Distribution is restricted and shall be done only with Developer's written approval.
18 * N.B. Requires MyAutomatic device available at
19 * http://www.ecomatiqhomes.com/#!store/tc3yr
22 import java.text.SimpleDateFormat
23 import groovy.json.JsonSlurper
26 name: "AutomaticReport",
28 author: "Yves Racine",
29 description: "This smartapp allows a ST user to manually generate daily Reports on their Automatic Connected vehicle",
31 iconUrl: "https://www.automatic.com/_assets/images/favicons/favicon-32x32-3df4de42.png",
32 iconX2Url: "https://www.automatic.com/_assets/images/favicons/favicon-96x96-06fd8c85.png",
33 iconX3Url: "https://www.automatic.com/_assets/images/favicons/favicon-96x96-06fd8c85.png")
37 paragraph "automaticReport, the smartapp that generates daily runtime reports about your Automatic connected vehicle"
38 paragraph "You can only run the smartapp manually by pressing the arrow sign on the app's icon"
39 paragraph "Version 1.6.8"
40 paragraph "If you like this smartapp, please support the developer via PayPal and click on the Paypal link below "
41 href url: "https://www.paypal.me/ecomatiqhomes",
42 title:"Paypal donation..."
43 paragraph "Copyright©2015 Yves Racine"
44 href url:"http://github.com/yracine/device-type.myautomatic", style:"embedded", required:false, title:"More information..."
45 description: "http://github.com/yracine"
48 section("Generate daily report for this Automatic Connected Vehicle") {
49 input "automatic", "capability.presenceSensor", title: "Automatic?"
52 section("Start date for the report, format = YYYY-MM-DD") {
53 input "givenStartDate", "text", title: "Beginning Date [default=yesterday]", required:false
55 section("Start time for report HH:MM (24HR)") {
56 input "givenStartTime", "text", title: "Beginning time [default=00:00]", required:false
58 section("End date for the report = YYYY-MM-DD") {
59 input "givenEndDate", "text", title: "End Date [default=today]", required:false
61 section("End time for the report (24HR)" ) {
62 input "givenEndTime", "text", title: "End time [default=00:00]", required:false
64 section( "Notifications" ) {
65 input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
66 input "phoneNumber", "phone", title: "Send a text message?", required: false
72 log.debug "automaticReport>installed with settings: ${settings}"
77 log.debug "Updated with settings: ${settings}"
85 subscribe(app, appTouch)
93 private def generateReport() {
94 def AUTOMATIC_SPEEDING_EVENT='speeding'
95 def AUTOMATIC_HARD_BRAKE_EVENT='hard_brake'
96 def AUTOMATIC_HARD_BRAKE_ACCEL='hard_accel'
99 String dateInLocalTime = new Date().format("yyyy-MM-dd", location.timeZone)
100 String timezone = new Date().format("zzz", location.timeZone)
101 String dateAtMidnight = dateInLocalTime + " 00:00 " + timezone
102 log.debug("generateReport>date at Midnight= ${dateAtMidnight}")
103 Date endDate = formatDate(dateAtMidnight)
104 Date startDate = endDate -1
106 def givenStartDate = (settings.givenStartDate) ?: startDate.format("yyyy-MM-dd", location.timeZone)
107 def givenStartTime=(settings.givenStartTime) ?:"00:00"
108 def dateTime = givenStartDate + " " + givenStartTime + " " + timezone
109 startDate = formatDate(dateTime)
110 log.debug("generateReport>start dateTime = ${dateTime}, startDate in UTC = ${startDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
112 def givenEndDate = (settings.givenEndDate) ?: endDate.format("yyyy-MM-dd", location.timeZone)
113 def givenEndTime=(settings.givenEndTime) ?:"00:00"
114 dateTime = givenEndDate + " " + givenEndTime + " " + timezone
115 endDate = formatDate(dateTime)
116 log.debug("generateReport>end dateTime = ${dateTime}, endDate in UTC =${endDate.format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"))}")
118 automatic.getTrips("","", null,null, null, true)
120 def currentTripList = automatic.currentTripsList
122 if (!currentTripList) {
123 log.debug "generateReport> empty tripList exiting"
126 String tripData = automatic.currentTripsData
128 log.debug("generateReport>no trip data found, exiting")
132 tripFields = new JsonSlurper().parseText(tripData)
133 // log.debug("generateReport> vehicle_events=$tripFields.vehicle_events[0]")
135 log.error("generateReport>tripData not formatted correctly or empty (exception $e), exiting")
138 def nbTripsValue = automatic.currentTotalNbTripsInPeriod
139 int nbTrips = (nbTripsValue)? nbTripsValue.toInteger():0
140 for (i in 0..nbTrips-1) {
141 def tripId=tripFields[i].id
142 String startAddress=tripFields[i].start_address.name
143 String endAddress=tripFields[i].end_address.name
144 def vehicleEvents=tripFields[i]?.vehicle_events
145 log.debug ("generateReport> found ${tripId} trip from start Address=${startAddress} to endAddress=${endAddress}, \nvehicleEvents=${vehicleEvents}")
146 String eventCreatedAt
149 if (type == AUTOMATIC_SPEEDING_EVENT) {
150 eventCreatedAt=formatDateInLocalTime(it.started_at)
151 def startDistance = it.start_distance_m
152 def endDistance= it.end_distance_m
153 float startPos= getDistance(startDistance)
154 float endPos= getDistance(endDistance)
155 log.debug ("generateReport> found ${type} event startedAt: ${it.started_at}, eventCreatedInLocalTime=$eventCreatedAt, timezone=${tripFields[i].end_timezone}")
156 def speed =it.velocity_kph
157 float speedValue=getSpeed(speed)
158 msg = "automaticReport>${automatic} was speeding (speed> ${speedValue.round()}${getSpeedScale()}) at ${eventCreatedAt} on trip ${tripId} from ${startAddress} to ${endAddress};" +
159 "Start Trip Distance=${startPos.round()}${getDistanceScale()},End Trip Distance=${endPos.round()}${getDistanceScale()}"
163 if (type ==AUTOMATIC_HARD_BRAKE_EVENT) {
164 eventCreatedAt=formatDateInLocalTime(it.created_at)
165 def gforce=it.g_force
168 log.debug ("generateReport> found ${type} event startedAt: ${it.created_at}, eventCreatedInLocalTime=$eventCreatedAt, timezone=${tripFields[i].end_timezone}")
169 msg = "automaticReport>${automatic} triggered the ${type} event (gforce=${gforce}, lat=${lat},lon=${lon}) at ${eventCreatedAt} on trip ${tripId} from ${startAddress} to ${endAddress} "
172 if (type==AUTOMATIC_HARD_ACCEL_EVENT) {
173 eventCreatedAt=formatDateInLocalTime(it.created_at)
174 def gforce=it.g_force
177 log.debug ("generateReport> found ${type} event startedAt: ${it.created_at}, eventCreatedInLocalTime=$eventCreatedAt, timezone=${tripFields[i].end_timezone}")
178 msg = "automaticReport>${automatic} triggered the ${type} event (gforce=${gforce}, lat=${lat},lon=${lon}) at ${eventCreatedAt} on trip ${tripId} from ${startAddress} to ${endAddress} "
181 } /* end each vehicle Event */
182 } /* end for each Trip */
187 private def report_states_between_dates(eventType, startDate, endDate) {
188 def eventStates = automatic.statesBetween("eventType", startDate as Date, endDate as Date)
190 def countEventType = eventStates.count {it.value && it.value == eventType}
192 def msg = "automaticReport>${automatic} triggered ${countEventType} ${eventType} event(s) between " +
193 "${startDate.format("yyyy-MM-dd HH:mm:ss")} and ${endDate.format("yyyy-MM-dd HH:mm:ss")}"
198 private def getSpeed(value) {
202 if(getTemperatureScale() == "C"){
205 return milesToKm(value)
209 private def getSpeedScale() {
210 def scale= getTemperatureScale()
217 private def milesToKm(distance) {
221 return (distance * 1.609344)
224 private def kmToMiles(distance) {
228 return (distance * 0.62137)
230 private def getDistance(value) {
235 if (getTemperatureScale() == "C"){
242 private def getDistanceScale() {
243 def scale= getTemperatureScale()
250 private String formatDateInLocalTime(dateInString, timezone='') {
251 def myTimezone=(timezone)?TimeZone.getTimeZone(timezone):location.timeZone
252 if ((dateInString==null) || (dateInString.trim()=="")) {
253 return (new Date().format("yyyy-MM-dd HH:mm:ss", myTimezone))
255 SimpleDateFormat ISODateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
256 Date ISODate = ISODateFormat.parse(dateInString.substring(0,19)+ 'Z')
257 String dateInLocalTime =new Date(ISODate.getTime()).format("yyyy-MM-dd HH:mm:ss zzz", myTimezone)
258 return dateInLocalTime
262 private def formatDate(dateString) {
263 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm zzz")
264 Date aDate = sdf.parse(dateString)
268 private def sendMsgWithDelay() {
277 if ( sendPushMessage != "No" ) {
278 log.debug( "sending push message" )
283 log.debug( "sending text message" )
284 sendSms( phoneNumber, msg )