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(thermostats, "heatingSetpoint", handleHeatingSetpointEvent)
106 subscribe(thermostats, "coolingSetpoint", handleCoolingSetpointEvent)
107 subscribe(thermostats, "thermostatMode", handleThermostatModeEvent)
108 subscribe(thermostats, "fanMode", handleFanModeEvent)
109 subscribe(thermostats, "thermostatOperatingState", handleThermostatOperatingStateEvent)
110 subscribe(automatic, "presence",handleDailyStats)
111 //subscribe(automatic, "yesterdayTripsAvgDistanceM",handleDailyStats)
112 //subscribe(automatic, "yesterdayTripsAvgDurationS",handleDailyStats)
113 //subscribe(automatic, "yesterdayTotalDistanceM",handleDailyStats)
114 //subscribe(automatic, "yesterdayTripsAvgFuelVolumeL",handleDailyStats)
115 //subscribe(automatic, "yesterdayTotalFuelVolumeL",handleDailyStats)
116 //subscribe(automatic, "yesterdayTotalDurationS:",handleDailyStats)
117 //subscribe(automatic, "yesterdayTotalNbTrips",handleDailyStats)
118 //subscribe(automatic, "yesterdayTotalHardAccels",handleDailyStats)
119 //subscribe(automatic, "yesterdayTotalHardBrakes:",handleDailyStats)
120 //subscribe(automatic, "yesterdayTripsAvgScoreSpeeding",handleDailyStats)
121 //subscribe(automatic, "yesterdayTripsAvgScoreEvents",handleDailyStats)
123 atomicState.queue=queue
125 if (atomicState.queue==null) {
126 atomicState.queue = []
128 atomicState?.poll = [ last: 0, rescheduled: now() ]
130 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
131 log.debug "initialize>scheduling processQueue every ${delay} minutes"
133 //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
134 subscribe(location, "sunrise", rescheduleIfNeeded)
135 subscribe(location, "sunset", rescheduleIfNeeded)
136 subscribe(location, "mode", rescheduleIfNeeded)
137 subscribe(location, "sunriseTime", rescheduleIfNeeded)
138 subscribe(location, "sunsetTime", rescheduleIfNeeded)
139 subscribe(app, appTouch)
141 //rescheduleIfNeeded()
145 rescheduleIfNeeded(evt)
148 atomicState.queue=queue
152 def rescheduleIfNeeded(evt) {
153 if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
154 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
155 BigDecimal currentTime = now()
156 BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))
157 if (lastPollTime != currentTime) {
158 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)
159 log.info "rescheduleIfNeeded>last poll was ${lastPollTimeInMinutes.toString()} minutes ago"
161 if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
162 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
164 //schedule("14:00", processQueue)
166 // Update rescheduled state
169 atomicState.poll["rescheduled"] = now()
173 def handleTemperatureEvent(evt) {
179 def handleHumidityEvent(evt) {
185 def handleHeatingSetpointEvent(evt) {
190 def handleCoolingSetpointEvent(evt) {
196 def handleThermostatModeEvent(evt) {
201 def handleFanModeEvent(evt) {
206 def handleHumidifierModeEvent(evt) {
211 def handleHumidifierLevelEvent(evt) {
216 def handleDehumidifierModeEvent(evt) {
221 def handleDehumidifierLevelEvent(evt) {
226 def handleVentilatorModeEvent(evt) {
231 def handleFanMinOnTimeEvent(evt) {
236 def handleVentilatorMinOnTimeEvent(evt) {
242 def handleThermostatOperatingStateEvent(evt) {
244 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
248 def handleDailyStats(evt) {
254 def handleEquipmentStatusEvent(evt) {
260 def handleProgramNameEvent(evt) {
266 def handleWaterEvent(evt) {
271 def handleSmokeEvent(evt) {
276 def handleCarbonMonoxideEvent(evt) {
282 def handleIlluminanceEvent(evt) {
283 log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
289 def handleLockEvent(evt) {
291 it == "locked" ? 1 : 0
295 def handleBatteryEvent(evt) {
301 def handleContactEvent(evt) {
307 def handleAccelerationEvent(evt) {
309 it == "active" ? 1 : 0
313 def handleMotionEvent(evt) {
315 it == "active" ? 1 : 0
319 def handlePresenceEvent(evt) {
321 it == "present" ? 1 : 0
325 def handleSwitchEvent(evt) {
331 def handleSetLevelEvent(evt) {
337 def handlePowerEvent(evt) {
345 def handleEnergyEvent(evt) {
352 def handleCostEvent(evt) {
360 private queueValue(evt, Closure convert) {
361 def MAX_QUEUE_SIZE=95000
362 def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
365 queue = atomicState.queue
367 atomicState.queue = queue
368 def queue_size = queue.toString().length()
369 def last_item_in_queue = queue[queue.size() -1]
370 log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
371 if (queue_size > MAX_QUEUE_SIZE) {
377 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
378 atomicState?.poll["last"] = now()
380 if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
381 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
382 //schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
383 // Update rescheduled state
384 atomicState?.poll["rescheduled"] = now()
387 def queue = atomicState.queue
390 def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
391 log.debug "processQueue"
393 log.debug "Events to be sent to groveStreams: ${queue}"
396 httpPutJson([uri: url, body: queue]) {response ->
397 if (response.status != 200) {
398 log.debug "GroveStreams logging failed, status = ${response.status}"
400 log.debug "GroveStreams accepted event(s)"
403 atomicState.queue = queue
406 } catch (groovyx.net.http.ResponseParseException e) {
407 // ignore error 200, bogus exception
408 if (e.statusCode != 200) {
409 log.error "Grovestreams: ${e}"
411 log.debug "GroveStreams accepted event(s)"
415 atomicState.queue = queue
418 def errorInfo = "Error sending value: ${e}"
422 atomicState.queue = queue