4 * Copyright 2014 Yves Racine
6 * LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
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.
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.
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
26 author: "Yves Racine",
27 description: "Log to groveStreams and send data streams based on devices selection",
29 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
30 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png"
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"
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
68 section("GroveStreams Feed PUT API key...") {
69 input "channelKey", "text", title: "API key"
71 section("Sending data at which interval in minutes (default=5)?") {
72 input "givenInterval", "number", title: 'Send Data Interval', required: false
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(ecobees, "dehumidifierMode", handleDehumidifierModeEvent)
112 subscribe(ecobees, "equipmentStatus", handleEquipmentStatusEvent)
113 subscribe(ecobees, "dehumidifierLevel", handleDehumidifierLevelEvent)
114 subscribe(ecobees, "humidifierMode", handleHumidifierModeEvent)
115 subscribe(ecobees, "humidifierLevel", handleHumidifierLevelEvent)
116 subscribe(ecobees, "fanMinOnTime", handleFanMinOnTimeEvent)
117 subscribe(ecobees, "ventilatorMode", handleVentilatorModeEvent)
118 subscribe(ecobees, "ventilatorMinOnTime", handleVentilatorMinOnTimeEvent)
119 subscribe(ecobees, "programScheduleName", handleProgramNameEvent)
120 subscribe(ecobees, "auxHeat1RuntimeDaily", handleDailyStats)
121 subscribe(ecobees, "auxHeat2RuntimeDaily", handleDailyStats)
122 subscribe(ecobees, "auxHeat3RuntimeDaily", handleDailyStats)
123 subscribe(ecobees, "compCool1RuntimeDaily", handleDailyStats)
124 subscribe(ecobees, "compCool2RuntimeDaily", handleDailyStats)
125 subscribe(ecobees, "fanRuntimeDaily", handleDailyStats)
126 subscribe(ecobees, "humidifierRuntimeDaily", handleDailyStats)
127 subscribe(ecobees, "dehumidifierRuntimeDaily", handleDailyStats)
128 subscribe(ecobees, "ventilatorRuntimeDaily", handleDailyStats)
129 subscribe(ecobees, "presence", handlePresenceEvent)
130 subscribe(ecobees, "compCool2RuntimeDaily", handleDailyStats)
131 subscribe(automatic, "yesterdayTripsAvgAverageKmpl",handleDailyStats)
132 subscribe(automatic, "yesterdayTripsAvgDistanceM",handleDailyStats)
133 subscribe(automatic, "yesterdayTripsAvgDurationS",handleDailyStats)
134 subscribe(automatic, "yesterdayTotalDistanceM",handleDailyStats)
135 subscribe(automatic, "yesterdayTripsAvgFuelVolumeL",handleDailyStats)
136 subscribe(automatic, "yesterdayTotalFuelVolumeL",handleDailyStats)
137 subscribe(automatic, "yesterdayTotalDurationS:",handleDailyStats)
138 subscribe(automatic, "yesterdayTotalNbTrips",handleDailyStats)
139 subscribe(automatic, "yesterdayTotalHardAccels",handleDailyStats)
140 subscribe(automatic, "yesterdayTotalHardBrakes:",handleDailyStats)
141 subscribe(automatic, "yesterdayTripsAvgScoreSpeeding",handleDailyStats)
142 subscribe(automatic, "yesterdayTripsAvgScoreEvents",handleDailyStats)
144 atomicState.queue=queue
146 if (atomicState.queue==null) {
147 atomicState.queue = []
149 atomicState?.poll = [ last: 0, rescheduled: now() ]
151 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
152 log.debug "initialize>scheduling processQueue every ${delay} minutes"
154 //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
155 subscribe(location, "sunrise", rescheduleIfNeeded)
156 subscribe(location, "sunset", rescheduleIfNeeded)
157 subscribe(location, "mode", rescheduleIfNeeded)
158 subscribe(location, "sunriseTime", rescheduleIfNeeded)
159 subscribe(location, "sunsetTime", rescheduleIfNeeded)
160 subscribe(app, appTouch)
169 atomicState.queue=queue
173 def rescheduleIfNeeded(evt) {
174 if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
175 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
176 BigDecimal currentTime = now()
177 BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))
178 if (lastPollTime != currentTime) {
179 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)
180 log.info "rescheduleIfNeeded>last poll was ${lastPollTimeInMinutes.toString()} minutes ago"
182 if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
183 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
185 schedule("0 0/${delay} * * * ?", processQueue)
187 // Update rescheduled state
190 atomicState.poll["rescheduled"] = now()
194 def handleTemperatureEvent(evt) {
200 def handleHumidityEvent(evt) {
206 def handleHeatingSetpointEvent(evt) {
211 def handleCoolingSetpointEvent(evt) {
217 def handleThermostatModeEvent(evt) {
222 def handleFanModeEvent(evt) {
227 def handleHumidifierModeEvent(evt) {
232 def handleHumidifierLevelEvent(evt) {
237 def handleDehumidifierModeEvent(evt) {
242 def handleDehumidifierLevelEvent(evt) {
247 def handleVentilatorModeEvent(evt) {
252 def handleFanMinOnTimeEvent(evt) {
257 def handleVentilatorMinOnTimeEvent(evt) {
263 def handleThermostatOperatingStateEvent(evt) {
265 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
269 def handleDailyStats(evt) {
275 def handleEquipmentStatusEvent(evt) {
281 def handleProgramNameEvent(evt) {
287 def handleWaterEvent(evt) {
292 def handleSmokeEvent(evt) {
297 def handleCarbonMonoxideEvent(evt) {
303 def handleIlluminanceEvent(evt) {
304 log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
310 def handleLockEvent(evt) {
312 it == "locked" ? 1 : 0
316 def handleBatteryEvent(evt) {
322 def handleContactEvent(evt) {
328 def handleAccelerationEvent(evt) {
330 it == "active" ? 1 : 0
334 def handleMotionEvent(evt) {
336 it == "active" ? 1 : 0
340 def handlePresenceEvent(evt) {
342 it == "present" ? 1 : 0
346 def handleSwitchEvent(evt) {
352 def handleSetLevelEvent(evt) {
358 def handlePowerEvent(evt) {
366 def handleEnergyEvent(evt) {
373 def handleCostEvent(evt) {
381 private queueValue(evt, Closure convert) {
382 def MAX_QUEUE_SIZE=95000
383 def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
386 queue = atomicState.queue
388 atomicState.queue = queue
389 def queue_size = queue.toString().length()
390 def last_item_in_queue = queue[queue.size() -1]
391 log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
392 if (queue_size > MAX_QUEUE_SIZE) {
398 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
399 atomicState?.poll["last"] = now()
401 if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
402 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
403 schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
404 // Update rescheduled state
405 atomicState?.poll["rescheduled"] = now()
408 def queue = atomicState.queue
411 def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
412 log.debug "processQueue"
414 log.debug "Events to be sent to groveStreams: ${queue}"
417 httpPutJson([uri: url, body: queue]) {response ->
418 if (response.status != 200) {
419 log.debug "GroveStreams logging failed, status = ${response.status}"
421 log.debug "GroveStreams accepted event(s)"
424 atomicState.queue = queue
427 } catch (groovyx.net.http.ResponseParseException e) {
428 // ignore error 200, bogus exception
429 if (e.statusCode != 200) {
430 log.error "Grovestreams: ${e}"
432 log.debug "GroveStreams accepted event(s)"
436 atomicState.queue = queue
439 def errorInfo = "Error sending value: ${e}"
443 atomicState.queue = queue