Skip to content

Commit a61ff35

Browse files
committed
db pooling: gather/parse data in softpoll
refactor parsing code out into it's own function. in the softpoll build all the data that will be sent to influxdb and post it all at once.
1 parent ec9ae32 commit a61ff35

File tree

1 file changed

+68
-63
lines changed

1 file changed

+68
-63
lines changed

smartapps/influxdb-logger/influxdb-logger.groovy

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,17 @@ preferences {
6868
input "prefDatabaseUser", "text", title: "Username", required: false
6969
input "prefDatabasePass", "text", title: "Password", required: false
7070
}
71-
71+
7272
section("Polling:") {
7373
input "prefSoftPollingInterval", "number", title:"Soft-Polling interval (minutes)", defaultValue: 10, required: true
7474
}
75-
75+
7676
section("System Monitoring:") {
7777
input "prefLogModeEvents", "bool", title:"Log Mode Events?", defaultValue: true, required: true
7878
input "prefLogHubProperties", "bool", title:"Log Hub Properties?", defaultValue: true, required: true
7979
input "prefLogLocationProperties", "bool", title:"Log Location Properties?", defaultValue: true, required: true
8080
}
81-
81+
8282
section("Devices To Monitor:") {
8383
input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
8484
input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
@@ -107,8 +107,8 @@ preferences {
107107
input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
108108
input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
109109
input "soundSensors", "capability.soundSensor", title: "Sound Sensors", multiple: true, required: false
110-
input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false
111-
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
110+
input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false
111+
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
112112
input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
113113
input "tamperAlerts", "capability.tamperAlert", title: "Tamper Alerts", multiple: true, required: false
114114
input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
@@ -137,7 +137,7 @@ preferences {
137137
def installed() {
138138
state.installedAt = now()
139139
state.loggingLevelIDE = 5
140-
log.debug "${app.label}: Installed with settings: ${settings}"
140+
log.debug "${app.label}: Installed with settings: ${settings}"
141141
}
142142

143143
/**
@@ -151,11 +151,11 @@ def uninstalled() {
151151

152152
/**
153153
* updated()
154-
*
154+
*
155155
* Runs when app settings are changed.
156-
*
156+
*
157157
* 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
158+
* Builds state.deviceAttributes which describes the attributes that will be monitored for each device collection
159159
* (used by manageSubscriptions() and softPoll()).
160160
* Refreshes scheduling and subscriptions.
161161
**/
@@ -164,24 +164,24 @@ def updated() {
164164

165165
// Update internal state:
166166
state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
167-
167+
168168
// Database config:
169169
state.databaseHost = settings.prefDatabaseHost
170170
state.databasePort = settings.prefDatabasePort
171171
state.databaseName = settings.prefDatabaseName
172172
state.databaseUser = settings.prefDatabaseUser
173-
state.databasePass = settings.prefDatabasePass
174-
173+
state.databasePass = settings.prefDatabasePass
174+
175175
state.path = "/write?db=${state.databaseName}"
176-
state.headers = [:]
176+
state.headers = [:]
177177
state.headers.put("HOST", "${state.databaseHost}:${state.databasePort}")
178178
state.headers.put("Content-Type", "application/x-www-form-urlencoded")
179179
if (state.databaseUser && state.databasePass) {
180180
state.headers.put("Authorization", encodeCredentialsBasic(state.databaseUser, state.databasePass))
181181
}
182182

183183
// 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
184+
// Note, the collection names are stored as strings. Adding references to the actual collection
185185
// objects causes major issues (possibly memory issues?).
186186
state.deviceAttributes = []
187187
state.deviceAttributes << [ devices: 'accelerometers', attributes: ['acceleration']]
@@ -211,8 +211,8 @@ def updated() {
211211
state.deviceAttributes << [ devices: 'sleepSensors', attributes: ['sleeping']]
212212
state.deviceAttributes << [ devices: 'smokeDetectors', attributes: ['smoke']]
213213
state.deviceAttributes << [ devices: 'soundSensors', attributes: ['sound']]
214-
state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
215-
state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
214+
state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
215+
state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
216216
state.deviceAttributes << [ devices: 'switchLevels', attributes: ['level']]
217217
state.deviceAttributes << [ devices: 'tamperAlerts', attributes: ['tamper']]
218218
state.deviceAttributes << [ devices: 'temperatures', attributes: ['temperature']]
@@ -228,7 +228,7 @@ def updated() {
228228
// Configure Scheduling:
229229
state.softPollingInterval = settings.prefSoftPollingInterval.toInteger()
230230
manageSchedules()
231-
231+
232232
// Configure Subscriptions:
233233
manageSubscriptions()
234234
}
@@ -239,18 +239,18 @@ def updated() {
239239

240240
/**
241241
* handleAppTouch(evt)
242-
*
242+
*
243243
* Used for testing.
244244
**/
245245
def handleAppTouch(evt) {
246246
logger("handleAppTouch()","trace")
247-
247+
248248
softPoll()
249249
}
250250

251251
/**
252252
* handleModeEvent(evt)
253-
*
253+
*
254254
* Log Mode changes.
255255
**/
256256
def handleModeEvent(evt) {
@@ -266,22 +266,29 @@ def handleModeEvent(evt) {
266266
/**
267267
* handleEvent(evt)
268268
*
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)
273-
*
274-
* Useful references:
275-
* - http://docs.smartthings.com/en/latest/capabilities-reference.html
276-
* - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
269+
* parseEvent() then post to InfluxDB.
277270
**/
278271
def handleEvent(evt) {
279272
logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
280-
273+
data = parseEvent(evt)
274+
postToInfluxDB(data)
275+
}
276+
277+
/**
278+
* parseEvent(evt)
279+
*
280+
* Parses event data to send to InfluxDB.
281+
* - Escapes and quotes string values.
282+
* - Calculates logical binary values where string values can be
283+
* represented as binary values (e.g. contact: closed = 1, open = 0)
284+
*
285+
**/
286+
def parseEvent(evt) {
281287
// Build data string to send to InfluxDB:
282288
// Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
283289
// If value is an integer, it must have a trailing "i"
284290
// If value is a string, it must be enclosed in double quotes.
291+
logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
285292
def measurement = evt.name
286293
// tags:
287294
def deviceId = escapeStringForInfluxDB(evt.deviceId)
@@ -297,9 +304,9 @@ def handleEvent(evt) {
297304
def unit = escapeStringForInfluxDB(evt.unit)
298305
def value = escapeStringForInfluxDB(evt.value)
299306
def valueBinary = ''
300-
307+
301308
def data = "${measurement},deviceId=${deviceId},deviceName=${deviceName},groupId=${groupId},groupName=${groupName},hubId=${hubId},hubName=${hubName},locationId=${locationId},locationName=${locationName}"
302-
309+
303310
// Unit tag and fields depend on the event type:
304311
// Most string-valued attributes can be translated to a binary value too.
305312
if ('acceleration' == evt.name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
@@ -474,18 +481,15 @@ def handleEvent(evt) {
474481
}
475482
// Catch any other event with a string value that hasn't been handled:
476483
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")
484+
logger("handleEvent(): Found a string value that's not explicitly handled: Device Name: ${deviceName}, Event Name: ${evt.name}, Value:${evt.value}","warn")
478485
value = '"' + value + '"'
479486
data += ",unit=${unit} value=${value}"
480487
}
481488
// Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
482489
else {
483490
data += ",unit=${unit} value=${value}"
484491
}
485-
486-
// Post data to InfluxDB:
487-
postToInfluxDB(data)
488-
492+
return data
489493
}
490494

491495

@@ -497,29 +501,30 @@ def handleEvent(evt) {
497501
* softPoll()
498502
*
499503
* Executed by schedule.
500-
*
504+
*
501505
* Forces data to be posted to InfluxDB (even if an event has not been triggered).
502506
* Doesn't poll devices, just builds a fake event to pass to handleEvent().
503507
*
504508
* Also calls LogSystemProperties().
505509
**/
506510
def softPoll() {
507511
logger("softPoll()","trace")
508-
512+
509513
logSystemProperties()
510-
514+
511515
// Iterate over each attribute for each device, in each device collection in deviceAttributes:
512516
def devs // temp variable to hold device collection.
517+
def eventsData = ""
513518
state.deviceAttributes.each { da ->
514519
devs = settings."${da.devices}"
515520
if (devs && (da.attributes)) {
516521
devs.each { d ->
517522
da.attributes.each { attr ->
518523
if (d.hasAttribute(attr) && d.latestState(attr)?.value != null) {
519524
logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
520-
// Send fake event to handleEvent():
521-
handleEvent([
522-
name: attr,
525+
// Build multiple event string to sent to InfluxDB
526+
eventsData = eventsData + '\n' + parseEvent([
527+
name: attr,
523528
value: d.latestState(attr)?.value,
524529
unit: d.latestState(attr)?.unit,
525530
device: d,
@@ -531,7 +536,7 @@ def softPoll() {
531536
}
532537
}
533538
}
534-
539+
postToInfluxDB(eventsData)
535540
}
536541

537542
/**
@@ -571,7 +576,7 @@ def logSystemProperties() {
571576
def hubIP = '"' + escapeStringForInfluxDB(h.localIP) + '"'
572577
def hubStatus = '"' + escapeStringForInfluxDB(h.status) + '"'
573578
def batteryInUse = ("false" == h.hub.getDataValue("batteryInUse")) ? "0i" : "1i"
574-
def hubUptime = h.hub.getDataValue("uptime") + 'i'
579+
def hubUptime = ("null" == h.hub.getDataValue("uptime")) ? (h.hub.getDataValue("uptime") + "i") : "0i"
575580
def zigbeePowerLevel = h.hub.getDataValue("zigbeePowerLevel") + 'i'
576581
def zwavePowerLevel = '"' + escapeStringForInfluxDB(h.hub.getDataValue("zwavePowerLevel")) + '"'
577582
def firmwareVersion = '"' + escapeStringForInfluxDB(h.firmwareVersionString) + '"'
@@ -597,7 +602,7 @@ def logSystemProperties() {
597602
**/
598603
def postToInfluxDB(data) {
599604
logger("postToInfluxDB(): Posting data to InfluxDB: Host: ${state.databaseHost}, Port: ${state.databasePort}, Database: ${state.databaseName}, Data: [${data}]","debug")
600-
605+
601606
try {
602607
def hubAction = new physicalgraph.device.HubAction(
603608
[
@@ -609,15 +614,15 @@ def postToInfluxDB(data) {
609614
null,
610615
[ callback: handleInfluxResponse ]
611616
)
612-
617+
613618
sendHubCommand(hubAction)
614619
}
615620
catch (Exception e) {
616621
logger("postToInfluxDB(): Exception ${e} on ${hubAction}","error")
617622
}
618623

619624
// For reference, code that could be used for WAN hosts:
620-
// def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
625+
// def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
621626
// try {
622627
// httpPost(url, data) { response ->
623628
// if (response.status != 999 ) {
@@ -626,7 +631,7 @@ def postToInfluxDB(data) {
626631
// log.debug "Response contentType: ${response.contentType}"
627632
// }
628633
// }
629-
// } catch (e) {
634+
// } catch (e) {
630635
// logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
631636
// }
632637
}
@@ -649,8 +654,8 @@ def handleInfluxResponse(physicalgraph.device.HubResponse hubResponse) {
649654

650655
/**
651656
* manageSchedules()
652-
*
653-
* Configures/restarts scheduled tasks:
657+
*
658+
* Configures/restarts scheduled tasks:
654659
* softPoll() - Run every {state.softPollingInterval} minutes.
655660
**/
656661
private manageSchedules() {
@@ -659,7 +664,7 @@ private manageSchedules() {
659664
// Generate a random offset (1-60):
660665
Random rand = new Random(now())
661666
def randomOffset = 0
662-
667+
663668
// softPoll:
664669
try {
665670
unschedule(softPoll)
@@ -673,26 +678,26 @@ private manageSchedules() {
673678
logger("manageSchedules(): Scheduling softpoll to run every ${state.softPollingInterval} minutes (offset of ${randomOffset} seconds).","trace")
674679
schedule("${randomOffset} 0/${state.softPollingInterval} * * * ?", "softPoll")
675680
}
676-
681+
677682
}
678683

679684
/**
680685
* manageSubscriptions()
681-
*
686+
*
682687
* Configures subscriptions.
683688
**/
684689
private manageSubscriptions() {
685690
logger("manageSubscriptions()","trace")
686691

687692
// Unsubscribe:
688693
unsubscribe()
689-
694+
690695
// Subscribe to App Touch events:
691696
subscribe(app,handleAppTouch)
692-
697+
693698
// Subscribe to mode events:
694699
if (prefLogModeEvents) subscribe(location, "mode", handleModeEvent)
695-
700+
696701
// Subscribe to device attributes (iterate over each attribute for each device collection in state.deviceAttributes):
697702
def devs // dynamic variable holding device collection.
698703
state.deviceAttributes.each { da ->
@@ -754,9 +759,9 @@ private encodeCredentialsBasic(username, password) {
754759
* escapeStringForInfluxDB()
755760
*
756761
* Escape values to InfluxDB.
757-
*
758-
* If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must
759-
* be escaped using the backslash character \. Backslash characters do not need to be escaped.
762+
*
763+
* If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must
764+
* be escaped using the backslash character \. Backslash characters do not need to be escaped.
760765
* Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
761766
*
762767
* Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
@@ -779,10 +784,10 @@ private escapeStringForInfluxDB(str) {
779784
* getGroupName()
780785
*
781786
* Get the name of a 'Group' (i.e. Room) from its ID.
782-
*
787+
*
783788
* This is done manually as there does not appear to be a way to enumerate
784789
* groups from a SmartApp currently.
785-
*
790+
*
786791
* GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
787792
*
788793
* See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
@@ -793,5 +798,5 @@ private getGroupName(id) {
793798
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Kitchen'}
794799
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Lounge'}
795800
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Office'}
796-
else {return 'Unknown'}
797-
}
801+
else {return 'Unknown'}
802+
}

0 commit comments

Comments
 (0)