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(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)
124 atomicState.queue=queue
126 if (atomicState.queue==null) {
127 atomicState.queue = []
129 atomicState?.poll = [ last: 0, rescheduled: now() ]
131 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
132 log.debug "initialize>scheduling processQueue every ${delay} minutes"
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)
142 //rescheduleIfNeeded()
146 rescheduleIfNeeded(evt)
149 atomicState.queue=queue
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"
162 if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
163 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
165 //schedule("14:00", processQueue)
167 // Update rescheduled state
170 atomicState.poll["rescheduled"] = now()
174 def handleTemperatureEvent(evt) {
180 def handleHumidityEvent(evt) {
186 def handleHeatingSetpointEvent(evt) {
191 def handleCoolingSetpointEvent(evt) {
197 def handleThermostatModeEvent(evt) {
202 def handleFanModeEvent(evt) {
207 def handleHumidifierModeEvent(evt) {
212 def handleHumidifierLevelEvent(evt) {
217 def handleDehumidifierModeEvent(evt) {
222 def handleDehumidifierLevelEvent(evt) {
227 def handleVentilatorModeEvent(evt) {
232 def handleFanMinOnTimeEvent(evt) {
237 def handleVentilatorMinOnTimeEvent(evt) {
243 def handleThermostatOperatingStateEvent(evt) {
245 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
249 def handleDailyStats(evt) {
255 def handleEquipmentStatusEvent(evt) {
261 def handleProgramNameEvent(evt) {
267 def handleWaterEvent(evt) {
272 def handleSmokeEvent(evt) {
277 def handleCarbonMonoxideEvent(evt) {
283 def handleIlluminanceEvent(evt) {
284 log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
290 def handleLockEvent(evt) {
292 it == "locked" ? 1 : 0
296 def handleBatteryEvent(evt) {
302 def handleContactEvent(evt) {
308 def handleAccelerationEvent(evt) {
310 it == "active" ? 1 : 0
314 def handleMotionEvent(evt) {
316 it == "active" ? 1 : 0
320 def handlePresenceEvent(evt) {
322 it == "present" ? 1 : 0
326 def handleSwitchEvent(evt) {
332 def handleSetLevelEvent(evt) {
338 def handlePowerEvent(evt) {
346 def handleEnergyEvent(evt) {
353 def handleCostEvent(evt) {
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()]
366 queue = atomicState.queue
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) {
378 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
379 atomicState?.poll["last"] = now()
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()
388 def queue = atomicState.queue
391 def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
392 log.debug "processQueue"
394 log.debug "Events to be sent to groveStreams: ${queue}"
397 httpPutJson([uri: url, body: queue]) {response ->
398 if (response.status != 200) {
399 log.debug "GroveStreams logging failed, status = ${response.status}"
401 log.debug "GroveStreams accepted event(s)"
404 atomicState.queue = queue
407 } catch (groovyx.net.http.ResponseParseException e) {
408 // ignore error 200, bogus exception
409 if (e.statusCode != 200) {
410 log.error "Grovestreams: ${e}"
412 log.debug "GroveStreams accepted event(s)"
416 atomicState.queue = queue
419 def errorInfo = "Error sending value: ${e}"
423 atomicState.queue = queue