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 "automatic", "capability.presenceSensor", title: "Automatic Connected Device(s)", required: false, multiple: true
50 input "detectors", "capability.smokeDetector", title: "Smoke/CarbonMonoxide Detectors", required: false, multiple: true
51 input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity sensors", required: false, multiple: true
52 input "waters", "capability.waterSensor", title: "Water sensors", required: false, multiple: true
53 input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance sensor", required: false, multiple: true
54 input "locks", "capability.lock", title: "Locks", required: false, multiple: true
55 input "contacts", "capability.contactSensor", title: "Doors open/close", required: false, multiple: true
56 input "accelerations", "capability.accelerationSensor", title: "Accelerations", required: false, multiple: true
57 input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true
58 input "presence", "capability.presenceSensor", title: "Presence", required: false, multiple: true
59 input "switches", "capability.switch", title: "Switches", required: false, multiple: true
60 input "dimmerSwitches", "capability.switchLevel", title: "Dimmer Switches", required: false, multiple: true
61 input "batteries", "capability.battery", title: "Battery-powered devices", required: false, multiple: true
62 input "powers", "capability.powerMeter", title: "Power Meters", required: false, multiple: true
63 input "energys", "capability.energyMeter", title: "Energy Meters", required: false, multiple: true
67 section("GroveStreams Feed PUT API key...") {
68 input "channelKey", "text", title: "API key"
70 section("Sending data at which interval in minutes (default=5)?") {
71 input "givenInterval", "number", title: 'Send Data Interval', required: false
86 subscribe(temperatures, "temperature", handleTemperatureEvent)
87 subscribe(humidities, "humidity", handleHumidityEvent)
88 subscribe(waters, "water", handleWaterEvent)
89 subscribe(waters, "water", handleWaterEvent)
90 subscribe(detectors, "smoke", handleSmokeEvent)
91 subscribe(detectors, "carbonMonoxide", handleCarbonMonoxideEvent)
92 subscribe(illuminances, "illuminance", handleIlluminanceEvent)
93 subscribe(contacts, "contact", handleContactEvent)
94 subscribe(locks, "lock", handleLockEvent)
95 subscribe(accelerations, "acceleration", handleAccelerationEvent)
96 subscribe(motions, "motion", handleMotionEvent)
97 subscribe(presence, "presence", handlePresenceEvent)
98 subscribe(switches, "switch", handleSwitchEvent)
99 subscribe(dimmerSwitches, "switch", handleSwitchEvent)
100 subscribe(dimmerSwitches, "level", handleSetLevelEvent)
101 subscribe(batteries, "battery", handleBatteryEvent)
102 subscribe(powers, "power", handlePowerEvent)
103 subscribe(energys, "energy", handleEnergyEvent)
104 subscribe(thermostats, "heatingSetpoint", handleHeatingSetpointEvent)
105 subscribe(thermostats, "coolingSetpoint", handleCoolingSetpointEvent)
106 subscribe(thermostats, "thermostatMode", handleThermostatModeEvent)
107 subscribe(thermostats, "fanMode", handleFanModeEvent)
108 subscribe(thermostats, "thermostatOperatingState", handleThermostatOperatingStateEvent)
109 subscribe(automatic, "presence",handleDailyStats)
112 atomicState.queue=queue
114 if (atomicState.queue==null) {
115 atomicState.queue = []
117 atomicState?.poll = [ last: 0, rescheduled: now() ]
119 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
120 log.debug "initialize>scheduling processQueue every ${delay} minutes"
122 //Subscribe to different events (ex. sunrise and sunset events) to trigger rescheduling if needed
123 subscribe(location, "sunrise", rescheduleIfNeeded)
124 subscribe(location, "sunset", rescheduleIfNeeded)
125 subscribe(location, "mode", rescheduleIfNeeded)
126 subscribe(location, "sunriseTime", rescheduleIfNeeded)
127 subscribe(location, "sunsetTime", rescheduleIfNeeded)
128 subscribe(app, appTouch)
130 //rescheduleIfNeeded()
134 rescheduleIfNeeded(evt)
137 atomicState.queue=queue
141 def rescheduleIfNeeded(evt) {
142 if (evt) log.debug("rescheduleIfNeeded>$evt.name=$evt.value")
143 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
144 BigDecimal currentTime = now()
145 BigDecimal lastPollTime = (currentTime - (atomicState?.poll["last"]?:0))
146 if (lastPollTime != currentTime) {
147 Double lastPollTimeInMinutes = (lastPollTime/60000).toDouble().round(1)
148 log.info "rescheduleIfNeeded>last poll was ${lastPollTimeInMinutes.toString()} minutes ago"
150 if (((atomicState?.poll["last"]?:0) + (delay * 60000) < currentTime) && canSchedule()) {
151 log.info "rescheduleIfNeeded>scheduling processQueue in ${delay} minutes.."
153 //schedule("14:00", processQueue)
155 // Update rescheduled state
158 atomicState.poll["rescheduled"] = now()
162 def handleTemperatureEvent(evt) {
168 def handleHumidityEvent(evt) {
174 def handleHeatingSetpointEvent(evt) {
179 def handleCoolingSetpointEvent(evt) {
185 def handleThermostatModeEvent(evt) {
190 def handleFanModeEvent(evt) {
195 def handleHumidifierModeEvent(evt) {
200 def handleHumidifierLevelEvent(evt) {
205 def handleDehumidifierModeEvent(evt) {
210 def handleDehumidifierLevelEvent(evt) {
215 def handleVentilatorModeEvent(evt) {
220 def handleFanMinOnTimeEvent(evt) {
225 def handleVentilatorMinOnTimeEvent(evt) {
231 def handleThermostatOperatingStateEvent(evt) {
233 it == "idle" ? 0 : (it == 'fan only') ? 1 : (it == 'heating') ? 2 : 3
237 def handleDailyStats(evt) {
243 def handleEquipmentStatusEvent(evt) {
249 def handleProgramNameEvent(evt) {
255 def handleWaterEvent(evt) {
260 def handleSmokeEvent(evt) {
265 def handleCarbonMonoxideEvent(evt) {
271 def handleIlluminanceEvent(evt) {
272 log.debug ("handleIlluminanceEvent> $evt.name= $evt.value")
278 def handleLockEvent(evt) {
280 it == "locked" ? 1 : 0
284 def handleBatteryEvent(evt) {
290 def handleContactEvent(evt) {
296 def handleAccelerationEvent(evt) {
298 it == "active" ? 1 : 0
302 def handleMotionEvent(evt) {
304 it == "active" ? 1 : 0
308 def handlePresenceEvent(evt) {
310 it == "present" ? 1 : 0
314 def handleSwitchEvent(evt) {
320 def handleSetLevelEvent(evt) {
326 def handlePowerEvent(evt) {
334 def handleEnergyEvent(evt) {
341 def handleCostEvent(evt) {
349 private queueValue(evt, Closure convert) {
350 def MAX_QUEUE_SIZE=95000
351 def jsonPayload = [compId: evt.displayName, streamId: evt.name, data: convert(evt.value), time: now()]
354 queue = atomicState.queue
356 atomicState.queue = queue
357 def queue_size = queue.toString().length()
358 def last_item_in_queue = queue[queue.size() -1]
359 log.debug "queueValue>queue size in chars=${queue_size}, appending ${jsonPayload} to queue, last item in queue= $last_item_in_queue"
360 if (queue_size > MAX_QUEUE_SIZE) {
366 Integer delay = givenInterval ?: 5 // By default, schedule processQueue every 5 min.
367 atomicState?.poll["last"] = now()
369 if (((atomicState?.poll["rescheduled"]?:0) + (delay * 60000)) < now()) {
370 log.info "processQueue>scheduling rescheduleIfNeeded() in ${delay} minutes.."
371 //schedule("0 0/${delay} * * * ?", rescheduleIfNeeded)
372 // Update rescheduled state
373 atomicState?.poll["rescheduled"] = now()
376 def queue = atomicState.queue
379 def url = "https://grovestreams.com/api/feed?api_key=${channelKey}"
380 log.debug "processQueue"
382 log.debug "Events to be sent to groveStreams: ${queue}"
385 httpPutJson([uri: url, body: queue]) {response ->
386 if (response.status != 200) {
387 log.debug "GroveStreams logging failed, status = ${response.status}"
389 log.debug "GroveStreams accepted event(s)"
392 atomicState.queue = queue
395 } catch (groovyx.net.http.ResponseParseException e) {
396 // ignore error 200, bogus exception
397 if (e.statusCode != 200) {
398 log.error "Grovestreams: ${e}"
400 log.debug "GroveStreams accepted event(s)"
404 atomicState.queue = queue
407 def errorInfo = "Error sending value: ${e}"
411 atomicState.queue = queue