1 /*****************************************************************************************************************
2 * Copyright David Lomas (codersaur)
4 * Name: InfluxDB Logger
10 * Source: https://github.com/codersaur/SmartThings/tree/master/smartapps/influxdb-logger
12 * Author: David Lomas (codersaur)
14 * Description: A SmartApp to log SmartThings device states to an InfluxDB database.
16 * For full information, including installation instructions, exmples, and version history, see:
17 * https://github.com/codersaur/SmartThings/tree/master/smartapps/influxdb-logger
19 * IMPORTANT - To enable the resolution of groupNames (i.e. room names), you must manually insert the group IDs
20 * into the getGroupName() command code at the end of this file.
23 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
24 * in compliance with the License. You may obtain a copy of the License at:
26 * http://www.apache.org/licenses/LICENSE-2.0
28 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
29 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
30 * for the specific language governing permissions and limitations under the License.
31 *****************************************************************************************************************/
33 name: "InfluxDB Logger",
34 namespace: "codersaur",
35 author: "David Lomas (codersaur)",
36 description: "Log SmartThings device states to InfluxDB",
38 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
39 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
40 iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
45 //input "prefDebugMode", "bool", title: "Enable debug logging?", defaultValue: true, displayDuringSetup: true
47 name: "configLoggingLevelIDE",
48 title: "IDE Live Logging Level:\nMessages with this level and higher will be logged to the IDE.",
59 displayDuringSetup: true,
64 section ("InfluxDB Database:") {
65 input "prefDatabaseHost", "text", title: "Host", defaultValue: "10.10.10.10", required: true
66 input "prefDatabasePort", "text", title: "Port", defaultValue: "8086", required: true
67 input "prefDatabaseName", "text", title: "Database Name", defaultValue: "", required: true
68 input "prefDatabaseUser", "text", title: "Username", required: false
69 input "prefDatabasePass", "text", title: "Password", required: false
73 input "prefSoftPollingInterval", "number", title:"Soft-Polling interval (minutes)", defaultValue: 10, required: true
76 section("System Monitoring:") {
77 input "prefLogModeEvents", "bool", title:"Log Mode Events?", defaultValue: true, required: true
78 input "prefLogHubProperties", "bool", title:"Log Hub Properties?", defaultValue: true, required: true
79 input "prefLogLocationProperties", "bool", title:"Log Location Properties?", defaultValue: true, required: true
82 section("Devices To Monitor:") {
83 input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
84 input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
85 input "batteries", "capability.battery", title: "Batteries", multiple: true, required: false
86 input "beacons", "capability.beacon", title: "Beacons", multiple: true, required: false
87 input "buttons", "capability.button", title: "Buttons", multiple: true, required: false
88 input "cos", "capability.carbonMonoxideDetector", title: "Carbon Monoxide Detectors", multiple: true, required: false
89 input "co2s", "capability.carbonDioxideMeasurement", title: "Carbon Dioxide Detectors", multiple: true, required: false//1
90 input "colors", "capability.colorControl", title: "Color Controllers", multiple: true, required: false
91 input "consumables", "capability.consumable", title: "Consumables", multiple: true, required: false//2
92 input "contacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
93 input "doorsControllers", "capability.doorControl", title: "Door Controllers", multiple: true, required: false
94 input "energyMeters", "capability.energyMeter", title: "Energy Meters", multiple: true, required: false
95 input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity Meters", multiple: true, required: false
96 input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance Meters", multiple: true, required: false
97 input "locks", "capability.lock", title: "Locks", multiple: true, required: false
98 input "motions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
99 input "musicPlayers", "capability.musicPlayer", title: "Music Players", multiple: true, required: false
100 input "peds", "capability.stepSensor", title: "Pedometers", multiple: true, required: false
101 input "phMeters", "capability.pHMeasurement", title: "pH Meters", multiple: true, required: false//3
102 input "powerMeters", "capability.powerMeter", title: "Power Meters", multiple: true, required: false
103 input "presences", "capability.presenceSensor", title: "Presence Sensors", multiple: true, required: false
104 input "pressures", "capability.sensor", title: "Pressure Sensors", multiple: true, required: false
105 input "shockSensors", "capability.shockSensor", title: "Shock Sensors", multiple: true, required: false//4
106 input "signalStrengthMeters", "capability.signalStrength", title: "Signal Strength Meters", multiple: true, required: false//5
107 input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
108 input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
109 input "soundSensors", "capability.soundSensor", title: "Sound Sensors", multiple: true, required: false//6
110 input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false//7
111 input "switches", "capability.switch", title: "Switches", multiple: true, required: false
112 input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
113 input "tamperAlerts", "capability.tamperAlert", title: "Tamper Alerts", multiple: true, required: false//8
114 input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
115 input "thermostats", "capability.thermostat", title: "Thermostats", multiple: true, required: false
116 input "threeAxis", "capability.threeAxis", title: "Three-axis (Orientation) Sensors", multiple: true, required: false
117 input "touchs", "capability.touchSensor", title: "Touch Sensors", multiple: true, required: false
118 input "uvs", "capability.ultravioletIndex", title: "UV Sensors", multiple: true, required: false//9
119 input "valves", "capability.valve", title: "Valves", multiple: true, required: false
120 input "volts", "capability.voltageMeasurement", title: "Voltage Meters", multiple: true, required: false//10
121 input "waterSensors", "capability.waterSensor", title: "Water Sensors", multiple: true, required: false
122 input "windowShades", "capability.windowShade", title: "Window Shades", multiple: true, required: false//11
128 /*****************************************************************************************************************
129 * SmartThings System Commands:
130 *****************************************************************************************************************/
135 * Runs when the app is first installed.
138 state.installedAt = now()
139 state.loggingLevelIDE = 5
140 log.debug "${app.label}: Installed with settings: ${settings}"
146 * Runs when the app is uninstalled.
149 logger("uninstalled()","trace")
155 * Runs when app settings are changed.
157 * Updates device.state with input values and other hard-coded values.
158 * Builds state.deviceAttributes which describes the attributes that will be monitored for each device collection
159 * (used by manageSubscriptions() and softPoll()).
160 * Refreshes scheduling and subscriptions.
163 logger("updated()","trace")
165 // Update internal state:
166 state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
169 state.databaseHost = settings.prefDatabaseHost
170 state.databasePort = settings.prefDatabasePort
171 state.databaseName = settings.prefDatabaseName
172 state.databaseUser = settings.prefDatabaseUser
173 state.databasePass = settings.prefDatabasePass
175 state.path = "/write?db=${state.databaseName}"
177 state.headers.put("HOST", "${state.databaseHost}:${state.databasePort}")
178 state.headers.put("Content-Type", "application/x-www-form-urlencoded")
179 if (state.databaseUser && state.databasePass) {
180 state.headers.put("Authorization", encodeCredentialsBasic(state.databaseUser, state.databasePass))
183 // Build array of device collections and the attributes we want to report on for that collection:
184 // Note, the collection names are stored as strings. Adding references to the actual collection
185 // objects causes major issues (possibly memory issues?).
186 state.deviceAttributes = []
187 state.deviceAttributes << [ devices: 'accelerometers', attributes: ['acceleration']]
188 state.deviceAttributes << [ devices: 'alarms', attributes: ['alarm']]
189 state.deviceAttributes << [ devices: 'batteries', attributes: ['battery']]
190 state.deviceAttributes << [ devices: 'beacons', attributes: ['presence']]
191 state.deviceAttributes << [ devices: 'buttons', attributes: ['button']]
192 state.deviceAttributes << [ devices: 'cos', attributes: ['carbonMonoxide']]
193 state.deviceAttributes << [ devices: 'co2s', attributes: ['carbonDioxide']]///
194 state.deviceAttributes << [ devices: 'colors', attributes: ['hue','saturation','color']]
195 state.deviceAttributes << [ devices: 'consumables', attributes: ['consumableStatus']]///
196 state.deviceAttributes << [ devices: 'contacts', attributes: ['contact']]
197 state.deviceAttributes << [ devices: 'doorsControllers', attributes: ['door']]
198 state.deviceAttributes << [ devices: 'energyMeters', attributes: ['energy']]
199 state.deviceAttributes << [ devices: 'humidities', attributes: ['humidity']]
200 state.deviceAttributes << [ devices: 'illuminances', attributes: ['illuminance']]
201 state.deviceAttributes << [ devices: 'locks', attributes: ['lock']]
202 state.deviceAttributes << [ devices: 'motions', attributes: ['motion']]
203 state.deviceAttributes << [ devices: 'musicPlayers', attributes: ['status','level','trackDescription','trackData','mute']]
204 state.deviceAttributes << [ devices: 'peds', attributes: ['steps','goal']]
205 state.deviceAttributes << [ devices: 'phMeters', attributes: ['pH']]///
206 state.deviceAttributes << [ devices: 'powerMeters', attributes: ['power']]
207 state.deviceAttributes << [ devices: 'presences', attributes: ['presence']]
208 state.deviceAttributes << [ devices: 'pressures', attributes: ['pressure']]///
209 state.deviceAttributes << [ devices: 'shockSensors', attributes: ['shock']]///
210 state.deviceAttributes << [ devices: 'signalStrengthMeters', attributes: ['lqi','rssi']]///
211 state.deviceAttributes << [ devices: 'sleepSensors', attributes: ['sleeping']]
212 state.deviceAttributes << [ devices: 'smokeDetectors', attributes: ['smoke']]
213 state.deviceAttributes << [ devices: 'soundSensors', attributes: ['sound']]///
214 state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]///
215 state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
216 state.deviceAttributes << [ devices: 'switchLevels', attributes: ['level']]
217 state.deviceAttributes << [ devices: 'tamperAlerts', attributes: ['tamper']]///
218 state.deviceAttributes << [ devices: 'temperatures', attributes: ['temperature']]
219 state.deviceAttributes << [ devices: 'thermostats', attributes: ['temperature','heatingSetpoint','coolingSetpoint','thermostatSetpoint','thermostatMode','thermostatFanMode','thermostatOperatingState']]
220 state.deviceAttributes << [ devices: 'threeAxis', attributes: ['threeAxis']]
221 state.deviceAttributes << [ devices: 'touchs', attributes: ['touch']]
222 state.deviceAttributes << [ devices: 'uvs', attributes: ['ultravioletIndex']]///
223 state.deviceAttributes << [ devices: 'valves', attributes: ['contact']]
224 state.deviceAttributes << [ devices: 'volts', attributes: ['voltage']]///
225 state.deviceAttributes << [ devices: 'waterSensors', attributes: ['water']]
226 state.deviceAttributes << [ devices: 'windowShades', attributes: ['windowShade']]///
228 // Configure Scheduling:
229 state.softPollingInterval = settings.prefSoftPollingInterval.toInteger()
232 // Configure Subscriptions:
233 manageSubscriptions()
236 /*****************************************************************************************************************
238 *****************************************************************************************************************/
241 * handleAppTouch(evt)
245 def handleAppTouch(evt) {
246 logger("handleAppTouch()","trace")
252 * handleModeEvent(evt)
256 def handleModeEvent(evt) {
257 logger("handleModeEvent(): Mode changed to: ${evt.value}","info")
259 def locationId = escapeStringForInfluxDB(location.id)
260 def locationName = escapeStringForInfluxDB(location.name)
261 def mode = '"' + escapeStringForInfluxDB(evt.value) + '"'
262 def data = "_stMode,locationId=${locationId},locationName=${locationName} mode=${mode}"
269 * Builds data to send to InfluxDB.
270 * - Escapes and quotes string values.
271 * - Calculates logical binary values where string values can be
272 * represented as binary values (e.g. contact: closed = 1, open = 0)
275 * - http://docs.smartthings.com/en/latest/capabilities-reference.html
276 * - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
278 def handleEvent(evt) {
279 logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
281 // Build data string to send to InfluxDB:
282 // Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
283 // If value is an integer, it must have a trailing "i"
284 // If value is a string, it must be enclosed in double quotes.
285 def measurement = evt.name
287 def deviceId = escapeStringForInfluxDB(evt.deviceId)
288 def deviceName = escapeStringForInfluxDB(evt.displayName)
289 def groupId = escapeStringForInfluxDB(evt?.device.device.groupId)
290 def groupName = escapeStringForInfluxDB(getGroupName(evt?.device.device.groupId))
291 def hubId = escapeStringForInfluxDB(evt?.device.device.hubId)
292 def hubName = escapeStringForInfluxDB(evt?.device.device.hub.toString())
293 // Don't pull these from the evt.device as the app itself will be associated with one location.
294 def locationId = escapeStringForInfluxDB(location.id)
295 def locationName = escapeStringForInfluxDB(location.name)
297 def unit = escapeStringForInfluxDB(evt.unit)
298 def value = escapeStringForInfluxDB(evt.value)
301 def data = "${measurement},deviceId=${deviceId},deviceName=${deviceName},groupId=${groupId},groupName=${groupName},hubId=${hubId},hubName=${hubName},locationId=${locationId},locationName=${locationName}"
303 // Unit tag and fields depend on the event type:
304 // Most string-valued attributes can be translated to a binary value too.
305 if ('acceleration' == evt.name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
306 unit = 'acceleration'
307 value = '"' + value + '"'
308 valueBinary = ('active' == evt.value) ? '1i' : '0i'
309 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
311 else if ('alarm' == evt.name) { // alarm: Calculate a binary value (strobe/siren/both = 1, off = 0)
313 value = '"' + value + '"'
314 valueBinary = ('off' == evt.value) ? '0i' : '1i'
315 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
317 else if ('button' == evt.name) { // button: Calculate a binary value (held = 1, pushed = 0)
319 value = '"' + value + '"'
320 valueBinary = ('pushed' == evt.value) ? '0i' : '1i'
321 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
323 else if ('carbonMonoxide' == evt.name) { // carbonMonoxide: Calculate a binary value (detected = 1, clear/tested = 0)
324 unit = 'carbonMonoxide'
325 value = '"' + value + '"'
326 valueBinary = ('detected' == evt.value) ? '1i' : '0i'
327 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
329 else if ('consumableStatus' == evt.name) { // consumableStatus: Calculate a binary value ("good" = 1, "missing"/"replace"/"maintenance_required"/"order" = 0)
330 unit = 'consumableStatus'
331 value = '"' + value + '"'
332 valueBinary = ('good' == evt.value) ? '1i' : '0i'
333 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
335 else if ('contact' == evt.name) { // contact: Calculate a binary value (closed = 1, open = 0)
337 value = '"' + value + '"'
338 valueBinary = ('closed' == evt.value) ? '1i' : '0i'
339 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
341 else if ('door' == evt.name) { // door: Calculate a binary value (closed = 1, open/opening/closing/unknown = 0)
343 value = '"' + value + '"'
344 valueBinary = ('closed' == evt.value) ? '1i' : '0i'
345 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
347 else if ('lock' == evt.name) { // door: Calculate a binary value (locked = 1, unlocked = 0)
349 value = '"' + value + '"'
350 valueBinary = ('locked' == evt.value) ? '1i' : '0i'
351 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
353 else if ('motion' == evt.name) { // Motion: Calculate a binary value (active = 1, inactive = 0)
355 value = '"' + value + '"'
356 valueBinary = ('active' == evt.value) ? '1i' : '0i'
357 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
359 else if ('mute' == evt.name) { // mute: Calculate a binary value (muted = 1, unmuted = 0)
361 value = '"' + value + '"'
362 valueBinary = ('muted' == evt.value) ? '1i' : '0i'
363 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
365 else if ('presence' == evt.name) { // presence: Calculate a binary value (present = 1, not present = 0)
367 value = '"' + value + '"'
368 valueBinary = ('present' == evt.value) ? '1i' : '0i'
369 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
371 else if ('shock' == evt.name) { // shock: Calculate a binary value (detected = 1, clear = 0)
373 value = '"' + value + '"'
374 valueBinary = ('detected' == evt.value) ? '1i' : '0i'
375 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
377 else if ('sleeping' == evt.name) { // sleeping: Calculate a binary value (sleeping = 1, not sleeping = 0)
379 value = '"' + value + '"'
380 valueBinary = ('sleeping' == evt.value) ? '1i' : '0i'
381 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
383 else if ('smoke' == evt.name) { // smoke: Calculate a binary value (detected = 1, clear/tested = 0)
385 value = '"' + value + '"'
386 valueBinary = ('detected' == evt.value) ? '1i' : '0i'
387 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
389 else if ('sound' == evt.name) { // sound: Calculate a binary value (detected = 1, not detected = 0)
391 value = '"' + value + '"'
392 valueBinary = ('detected' == evt.value) ? '1i' : '0i'
393 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
395 else if ('switch' == evt.name) { // switch: Calculate a binary value (on = 1, off = 0)
397 value = '"' + value + '"'
398 valueBinary = ('on' == evt.value) ? '1i' : '0i'
399 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
401 else if ('tamper' == evt.name) { // tamper: Calculate a binary value (detected = 1, clear = 0)
403 value = '"' + value + '"'
404 valueBinary = ('detected' == evt.value) ? '1i' : '0i'
405 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
407 else if ('thermostatMode' == evt.name) { // thermostatMode: Calculate a binary value (<any other value> = 1, off = 0)
408 unit = 'thermostatMode'
409 value = '"' + value + '"'
410 valueBinary = ('off' == evt.value) ? '0i' : '1i'
411 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
413 else if ('thermostatFanMode' == evt.name) { // thermostatFanMode: Calculate a binary value (<any other value> = 1, off = 0)
414 unit = 'thermostatFanMode'
415 value = '"' + value + '"'
416 valueBinary = ('off' == evt.value) ? '0i' : '1i'
417 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
419 else if ('thermostatOperatingState' == evt.name) { // thermostatOperatingState: Calculate a binary value (heating = 1, <any other value> = 0)
420 unit = 'thermostatOperatingState'
421 value = '"' + value + '"'
422 valueBinary = ('heating' == evt.value) ? '1i' : '0i'
423 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
425 else if ('thermostatSetpointMode' == evt.name) { // thermostatSetpointMode: Calculate a binary value (followSchedule = 0, <any other value> = 1)
426 unit = 'thermostatSetpointMode'
427 value = '"' + value + '"'
428 valueBinary = ('followSchedule' == evt.value) ? '0i' : '1i'
429 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
431 else if ('threeAxis' == evt.name) { // threeAxis: Format to x,y,z values.
433 def valueXYZ = evt.value.split(",")
434 def valueX = valueXYZ[0]
435 def valueY = valueXYZ[1]
436 def valueZ = valueXYZ[2]
437 data += ",unit=${unit} valueX=${valueX}i,valueY=${valueY}i,valueZ=${valueZ}i" // values are integers.
439 else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, "" = 0)
441 value = '"' + value + '"'
442 valueBinary = ('touched' == evt.value) ? '1i' : '0i'
443 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
445 else if ('optimisation' == evt.name) { // optimisation: Calculate a binary value (active = 1, inactive = 0)
446 unit = 'optimisation'
447 value = '"' + value + '"'
448 valueBinary = ('active' == evt.value) ? '1i' : '0i'
449 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
451 else if ('windowFunction' == evt.name) { // windowFunction: Calculate a binary value (active = 1, inactive = 0)
452 unit = 'windowFunction'
453 value = '"' + value + '"'
454 valueBinary = ('active' == evt.value) ? '1i' : '0i'
455 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
457 else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, <any other value> = 0)
459 value = '"' + value + '"'
460 valueBinary = ('touched' == evt.value) ? '1i' : '0i'
461 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
463 else if ('water' == evt.name) { // water: Calculate a binary value (wet = 1, dry = 0)
465 value = '"' + value + '"'
466 valueBinary = ('wet' == evt.value) ? '1i' : '0i'
467 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
469 else if ('windowShade' == evt.name) { // windowShade: Calculate a binary value (closed = 1, <any other value> = 0)
471 value = '"' + value + '"'
472 valueBinary = ('closed' == evt.value) ? '1i' : '0i'
473 data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
475 // Catch any other event with a string value that hasn't been handled:
476 else if (evt.value ==~ /.*[^0-9\.,-].*/) { // match if any characters are not digits, period, comma, or hyphen.
477 logger("handleEvent(): Found a string value that's not explicitly handled: Device Name: ${deviceName}, Event Name: ${evt.name}, Value: ${evt.value}","warn")
478 value = '"' + value + '"'
479 data += ",unit=${unit} value=${value}"
481 // Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
483 data += ",unit=${unit} value=${value}"
486 // Post data to InfluxDB:
492 /*****************************************************************************************************************
494 *****************************************************************************************************************/
499 * Executed by schedule.
501 * Forces data to be posted to InfluxDB (even if an event has not been triggered).
502 * Doesn't poll devices, just builds a fake event to pass to handleEvent().
504 * Also calls LogSystemProperties().
507 logger("softPoll()","trace")
509 logSystemProperties()
511 // Iterate over each attribute for each device, in each device collection in deviceAttributes:
512 def devs // temp variable to hold device collection.
513 state.deviceAttributes.each { da ->
514 devs = settings."${da.devices}"
515 if (devs && (da.attributes)) {
517 da.attributes.each { attr ->
518 if (d.hasAttribute(attr) && d.latestState(attr)?.value != null) {
519 logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
520 // Send fake event to handleEvent():
523 value: d.latestState(attr)?.value,
524 unit: d.latestState(attr)?.unit,
527 displayName: d.displayName
538 * logSystemProperties()
540 * Generates measurements for SmartThings system (hubs and locations) properties.
542 def logSystemProperties() {
543 logger("logSystemProperties()","trace")
545 def locationId = '"' + escapeStringForInfluxDB(location.id) + '"'
546 def locationName = '"' + escapeStringForInfluxDB(location.name) + '"'
548 // Location Properties:
549 if (prefLogLocationProperties) {
551 def tz = '"' + escapeStringForInfluxDB(location.timeZone.ID) + '"'
552 def mode = '"' + escapeStringForInfluxDB(location.mode) + '"'
553 def hubCount = location.hubs.size()
554 def times = getSunriseAndSunset()
555 def srt = '"' + times.sunrise.format("HH:mm", location.timeZone) + '"'
556 def sst = '"' + times.sunset.format("HH:mm", location.timeZone) + '"'
558 def data = "_stLocation,locationId=${locationId},locationName=${locationName},latitude=${location.latitude},longitude=${location.longitude},timeZone=${tz} mode=${mode},hubCount=${hubCount}i,sunriseTime=${srt},sunsetTime=${sst}"
561 logger("logSystemProperties(): Unable to log Location properties: ${e}","error")
566 if (prefLogHubProperties) {
567 location.hubs.each { h ->
569 def hubId = '"' + escapeStringForInfluxDB(h.id) + '"'
570 def hubName = '"' + escapeStringForInfluxDB(h.name) + '"'
571 def hubIP = '"' + escapeStringForInfluxDB(h.localIP) + '"'
572 def hubStatus = '"' + escapeStringForInfluxDB(h.status) + '"'
573 def batteryInUse = ("false" == h.hub.getDataValue("batteryInUse")) ? "0i" : "1i"
574 def hubUptime = h.hub.getDataValue("uptime") + 'i'
575 def zigbeePowerLevel = h.hub.getDataValue("zigbeePowerLevel") + 'i'
576 def zwavePowerLevel = '"' + escapeStringForInfluxDB(h.hub.getDataValue("zwavePowerLevel")) + '"'
577 def firmwareVersion = '"' + escapeStringForInfluxDB(h.firmwareVersionString) + '"'
579 def data = "_stHub,locationId=${locationId},locationName=${locationName},hubId=${hubId},hubName=${hubName},hubIP=${hubIP} "
580 data += "status=${hubStatus},batteryInUse=${batteryInUse},uptime=${hubUptime},zigbeePowerLevel=${zigbeePowerLevel},zwavePowerLevel=${zwavePowerLevel},firmwareVersion=${firmwareVersion}"
583 logger("logSystemProperties(): Unable to log Hub properties: ${e}","error")
594 * Posts data to InfluxDB.
596 * Uses hubAction instead of httpPost() in case InfluxDB server is on the same LAN as the Smartthings Hub.
598 def postToInfluxDB(data) {
599 logger("postToInfluxDB(): Posting data to InfluxDB: Host: ${state.databaseHost}, Port: ${state.databasePort}, Database: ${state.databaseName}, Data: [${data}]","debug")
602 def hubAction = new physicalgraph.device.HubAction(
607 headers: state.headers
610 [ callback: handleInfluxResponse ]
613 sendHubCommand(hubAction)
615 catch (Exception e) {
616 logger("postToInfluxDB(): Exception ${e} on ${hubAction}","error")
619 // For reference, code that could be used for WAN hosts:
620 // def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
622 // httpPost(url, data) { response ->
623 // if (response.status != 999 ) {
624 // log.debug "Response Status: ${response.status}"
625 // log.debug "Response data: ${response.data}"
626 // log.debug "Response contentType: ${response.contentType}"
630 // logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
635 * handleInfluxResponse()
637 * Handles response from post made in postToInfluxDB().
639 /*def handleInfluxResponse(physicalgraph.device.HubResponse hubResponse) {
640 if(hubResponse.status >= 400) {
641 logger("postToInfluxDB(): Something went wrong! Response from InfluxDB: Headers: ${hubResponse.headers}, Body: ${hubResponse.body}","error")
646 /*****************************************************************************************************************
647 * Private Helper Functions:
648 *****************************************************************************************************************/
653 * Configures/restarts scheduled tasks:
654 * softPoll() - Run every {state.softPollingInterval} minutes.
656 private manageSchedules() {
657 logger("manageSchedules()","trace")
659 // Generate a random offset (1-60):
660 Random rand = new Random(now())
668 // logger("manageSchedules(): Unschedule failed!","error")
671 if (state.softPollingInterval > 0) {
672 randomOffset = rand.nextInt(60)
673 logger("manageSchedules(): Scheduling softpoll to run every ${state.softPollingInterval} minutes (offset of ${randomOffset} seconds).","trace")
674 schedule("${randomOffset} 0/${state.softPollingInterval} * * * ?", "softPoll")
680 * manageSubscriptions()
682 * Configures subscriptions.
684 private manageSubscriptions() {
685 logger("manageSubscriptions()","trace")
690 // Subscribe to App Touch events:
691 subscribe(app,handleAppTouch)
693 // Subscribe to mode events:
694 if (prefLogModeEvents) subscribe(location, "mode", handleModeEvent)
696 subscribe(accelerometers, "acceleration", handleEvent)
697 subscribe(alarms, "alarm", handleEvent)
698 subscribe(batteries, "battery", handleEvent)
699 subscribe(beacons, "presence", handleEvent)
700 subscribe(buttons, "button", handleEvent)
701 subscribe(cos, "carbonMonoxide", handleEvent)
702 subscribe(co2s, "carbonDioxide", handleEvent)
703 subscribe(colors, "hue", handleEvent)
704 subscribe(colors, "saturation", handleEvent)
705 subscribe(colors, "color", handleEvent)
706 subscribe(consumables, "consumableStatus", handleEvent)
707 subscribe(contacts, "contact", handleEvent)
708 subscribe(doorsControllers, "doorState", handleEvent)
709 subscribe(energyMeters, "energy", handleEvent)
710 subscribe(humidities, "humidity", handleEvent)
711 subscribe(illuminances, "illuminance", handleEvent)
712 subscribe(locks, "lock", handleEvent)
713 subscribe(motions, "motion", handleEvent)
714 subscribe(musicPlayers, "status", handleEvent)
715 subscribe(musicPlayers, "level", handleEvent)
716 subscribe(musicPlayers, "trackDescription", handleEvent)
717 subscribe(musicPlayers, "trackData", handleEvent)
718 subscribe(musicPlayers, "mute", handleEvent)
719 subscribe(peds, "steps", handleEvent)
720 subscribe(peds, "goal", handleEvent)
721 subscribe(phMeters, "pH", handleEvent)
722 subscribe(powerMeters, "power", handleEvent)
723 subscribe(presences, "presence", handleEvent)
724 subscribe(pressures, "pressure", handleEvent)
725 subscribe(shockSensors, "shock", handleEvent)
726 subscribe(signalStrengthMeters, "lqi", handleEvent)
727 subscribe(signalStrengthMeters, "rssi", handleEvent)
728 subscribe(sleepSensors, "sleeping", handleEvent)
729 subscribe(smokeDetectors, "smoke", handleEvent)
730 subscribe(soundSensors, "sound", handleEvent)
731 subscribe(spls, "soundPressureLevel", handleEvent)
732 subscribe(switches, "switch", handleEvent)
733 subscribe(switchLevels, "level", handleEvent)
734 subscribe(tamperAlerts, "tamper", handleEvent)
735 subscribe(temperatures, "temperature", handleEvent)
736 subscribe(thermostats, "temperature", handleEvent)
737 subscribe(thermostats, "heatingSetpoint", handleEvent)
738 subscribe(thermostats, "coolingSetpoint", handleEvent)
739 subscribe(thermostats, "thermostatSetpoint", handleEvent)
740 subscribe(thermostats, "thermostatMode", handleEvent)
741 subscribe(thermostats, "thermostatFanMode", handleEvent)
742 subscribe(thermostats, "thermostatOperatingState", handleEvent)
743 subscribe(threeAxis, "threeAxis", handleEvent)
744 subscribe(touchs, "touched", handleEvent)
745 subscribe(uvs, "ultravioletIndex", handleEvent)
746 subscribe(valves, "contact", handleEvent)
747 subscribe(volts, "voltage", handleEvent)
748 subscribe(waterSensors, "water", handleEvent)
749 subscribe(windowShades, "windowShade", handleEvent)
755 * Wrapper function for all logging.
757 private logger(msg, level = "debug") {
761 if (state.loggingLevelIDE >= 1) log.error msg
765 if (state.loggingLevelIDE >= 2) log.warn msg
769 if (state.loggingLevelIDE >= 3) log.info msg
773 if (state.loggingLevelIDE >= 4) log.debug msg
777 if (state.loggingLevelIDE >= 5) log.trace msg
787 * encodeCredentialsBasic()
789 * Encode credentials for HTTP Basic authentication.
791 private encodeCredentialsBasic(username, password) {
792 return "Basic " + "${username}:${password}".encodeAsBase64().toString()
796 * escapeStringForInfluxDB()
798 * Escape values to InfluxDB.
800 * If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must
801 * be escaped using the backslash character \. Backslash characters do not need to be escaped.
802 * Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
804 * Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
806 private escapeStringForInfluxDB(str) {
808 str = str.replaceAll(" ", "\\\\ ") // Escape spaces.
809 str = str.replaceAll(",", "\\\\,") // Escape commas.
810 str = str.replaceAll("=", "\\\\=") // Escape equal signs.
811 str = str.replaceAll("\"", "\\\\\"") // Escape double quotes.
812 //str = str.replaceAll("'", "_") // Replace apostrophes with underscores.
823 * Get the name of a 'Group' (i.e. Room) from its ID.
825 * This is done manually as there does not appear to be a way to enumerate
826 * groups from a SmartApp currently.
828 * GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
830 * See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
832 private getGroupName(id) {
834 if (id == null) {return 'Home'}
835 else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Kitchen'}
836 else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Lounge'}
837 else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Office'}
838 else {return 'Unknown'}