Skip to content

Commit 57398e3

Browse files
committed
db pooling: add ability to pool db writes
Enabled by default. This handles only the softPolling() events. Misc cleanup and handle possibility of a null uptime
1 parent ec9ae32 commit 57398e3

File tree

1 file changed

+89
-53
lines changed

1 file changed

+89
-53
lines changed

smartapps/influxdb-logger/influxdb-logger.groovy

Lines changed: 89 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,19 @@ preferences {
6767
input "prefDatabaseName", "text", title: "Database Name", defaultValue: "", required: true
6868
input "prefDatabaseUser", "text", title: "Username", required: false
6969
input "prefDatabasePass", "text", title: "Password", required: false
70+
input "preDatabasePooling", "bool", title: "Enable Pooling", defaultValue: true, required: true
7071
}
71-
72+
7273
section("Polling:") {
7374
input "prefSoftPollingInterval", "number", title:"Soft-Polling interval (minutes)", defaultValue: 10, required: true
7475
}
75-
76+
7677
section("System Monitoring:") {
7778
input "prefLogModeEvents", "bool", title:"Log Mode Events?", defaultValue: true, required: true
7879
input "prefLogHubProperties", "bool", title:"Log Hub Properties?", defaultValue: true, required: true
7980
input "prefLogLocationProperties", "bool", title:"Log Location Properties?", defaultValue: true, required: true
8081
}
81-
82+
8283
section("Devices To Monitor:") {
8384
input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
8485
input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
@@ -107,8 +108,8 @@ preferences {
107108
input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
108109
input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
109110
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
111+
input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false
112+
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
112113
input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
113114
input "tamperAlerts", "capability.tamperAlert", title: "Tamper Alerts", multiple: true, required: false
114115
input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
@@ -137,7 +138,7 @@ preferences {
137138
def installed() {
138139
state.installedAt = now()
139140
state.loggingLevelIDE = 5
140-
log.debug "${app.label}: Installed with settings: ${settings}"
141+
log.debug "${app.label}: Installed with settings: ${settings}"
141142
}
142143

143144
/**
@@ -151,11 +152,11 @@ def uninstalled() {
151152

152153
/**
153154
* updated()
154-
*
155+
*
155156
* Runs when app settings are changed.
156-
*
157+
*
157158
* 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+
* Builds state.deviceAttributes which describes the attributes that will be monitored for each device collection
159160
* (used by manageSubscriptions() and softPoll()).
160161
* Refreshes scheduling and subscriptions.
161162
**/
@@ -164,24 +165,25 @@ def updated() {
164165

165166
// Update internal state:
166167
state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
167-
168+
168169
// Database config:
169170
state.databaseHost = settings.prefDatabaseHost
170171
state.databasePort = settings.prefDatabasePort
171172
state.databaseName = settings.prefDatabaseName
172173
state.databaseUser = settings.prefDatabaseUser
173-
state.databasePass = settings.prefDatabasePass
174-
174+
state.databasePass = settings.prefDatabasePass
175+
state.databasePool = settings.prefDatabasePooling
176+
175177
state.path = "/write?db=${state.databaseName}"
176-
state.headers = [:]
178+
state.headers = [:]
177179
state.headers.put("HOST", "${state.databaseHost}:${state.databasePort}")
178180
state.headers.put("Content-Type", "application/x-www-form-urlencoded")
179181
if (state.databaseUser && state.databasePass) {
180182
state.headers.put("Authorization", encodeCredentialsBasic(state.databaseUser, state.databasePass))
181183
}
182184

183185
// 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
186+
// Note, the collection names are stored as strings. Adding references to the actual collection
185187
// objects causes major issues (possibly memory issues?).
186188
state.deviceAttributes = []
187189
state.deviceAttributes << [ devices: 'accelerometers', attributes: ['acceleration']]
@@ -211,8 +213,8 @@ def updated() {
211213
state.deviceAttributes << [ devices: 'sleepSensors', attributes: ['sleeping']]
212214
state.deviceAttributes << [ devices: 'smokeDetectors', attributes: ['smoke']]
213215
state.deviceAttributes << [ devices: 'soundSensors', attributes: ['sound']]
214-
state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
215-
state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
216+
state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
217+
state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
216218
state.deviceAttributes << [ devices: 'switchLevels', attributes: ['level']]
217219
state.deviceAttributes << [ devices: 'tamperAlerts', attributes: ['tamper']]
218220
state.deviceAttributes << [ devices: 'temperatures', attributes: ['temperature']]
@@ -228,7 +230,10 @@ def updated() {
228230
// Configure Scheduling:
229231
state.softPollingInterval = settings.prefSoftPollingInterval.toInteger()
230232
manageSchedules()
231-
233+
234+
// Configure if we're pooling InfluxPooling
235+
state.poolData = ""
236+
232237
// Configure Subscriptions:
233238
manageSubscriptions()
234239
}
@@ -239,18 +244,18 @@ def updated() {
239244

240245
/**
241246
* handleAppTouch(evt)
242-
*
247+
*
243248
* Used for testing.
244249
**/
245250
def handleAppTouch(evt) {
246251
logger("handleAppTouch()","trace")
247-
252+
248253
softPoll()
249254
}
250255

251256
/**
252257
* handleModeEvent(evt)
253-
*
258+
*
254259
* Log Mode changes.
255260
**/
256261
def handleModeEvent(evt) {
@@ -268,16 +273,16 @@ def handleModeEvent(evt) {
268273
*
269274
* Builds data to send to InfluxDB.
270275
* - Escapes and quotes string values.
271-
* - Calculates logical binary values where string values can be
276+
* - Calculates logical binary values where string values can be
272277
* represented as binary values (e.g. contact: closed = 1, open = 0)
273-
*
274-
* Useful references:
278+
*
279+
* Useful references:
275280
* - http://docs.smartthings.com/en/latest/capabilities-reference.html
276281
* - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
277282
**/
278283
def handleEvent(evt) {
279284
logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
280-
285+
281286
// Build data string to send to InfluxDB:
282287
// Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
283288
// If value is an integer, it must have a trailing "i"
@@ -297,9 +302,9 @@ def handleEvent(evt) {
297302
def unit = escapeStringForInfluxDB(evt.unit)
298303
def value = escapeStringForInfluxDB(evt.value)
299304
def valueBinary = ''
300-
305+
301306
def data = "${measurement},deviceId=${deviceId},deviceName=${deviceName},groupId=${groupId},groupName=${groupName},hubId=${hubId},hubName=${hubName},locationId=${locationId},locationName=${locationName}"
302-
307+
303308
// Unit tag and fields depend on the event type:
304309
// Most string-valued attributes can be translated to a binary value too.
305310
if ('acceleration' == evt.name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
@@ -474,17 +479,22 @@ def handleEvent(evt) {
474479
}
475480
// Catch any other event with a string value that hasn't been handled:
476481
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")
482+
logger("handleEvent(): Found a string value that's not explicitly handled: Device Name: ${deviceName}, Event Name: ${evt.name}, Value:${evt.value}","warn")
478483
value = '"' + value + '"'
479484
data += ",unit=${unit} value=${value}"
480485
}
481486
// Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
482487
else {
483488
data += ",unit=${unit} value=${value}"
484489
}
485-
486-
// Post data to InfluxDB:
487-
postToInfluxDB(data)
490+
491+
// Post data to InfluxDB pool requests if enabled.
492+
if (settings.preDatabasePooling) {
493+
poolInfluxDB(data)
494+
}
495+
else {
496+
postToInfluxDB(data)
497+
}
488498

489499
}
490500

@@ -497,17 +507,17 @@ def handleEvent(evt) {
497507
* softPoll()
498508
*
499509
* Executed by schedule.
500-
*
510+
*
501511
* Forces data to be posted to InfluxDB (even if an event has not been triggered).
502512
* Doesn't poll devices, just builds a fake event to pass to handleEvent().
503513
*
504514
* Also calls LogSystemProperties().
505515
**/
506516
def softPoll() {
507517
logger("softPoll()","trace")
508-
518+
509519
logSystemProperties()
510-
520+
511521
// Iterate over each attribute for each device, in each device collection in deviceAttributes:
512522
def devs // temp variable to hold device collection.
513523
state.deviceAttributes.each { da ->
@@ -519,7 +529,7 @@ def softPoll() {
519529
logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
520530
// Send fake event to handleEvent():
521531
handleEvent([
522-
name: attr,
532+
name: attr,
523533
value: d.latestState(attr)?.value,
524534
unit: d.latestState(attr)?.unit,
525535
device: d,
@@ -571,7 +581,7 @@ def logSystemProperties() {
571581
def hubIP = '"' + escapeStringForInfluxDB(h.localIP) + '"'
572582
def hubStatus = '"' + escapeStringForInfluxDB(h.status) + '"'
573583
def batteryInUse = ("false" == h.hub.getDataValue("batteryInUse")) ? "0i" : "1i"
574-
def hubUptime = h.hub.getDataValue("uptime") + 'i'
584+
def hubUptime = ("null" == h.hub.getDataValue("uptime")) ? (h.hub.getDataValue("uptime") + "i") : "0i"
575585
def zigbeePowerLevel = h.hub.getDataValue("zigbeePowerLevel") + 'i'
576586
def zwavePowerLevel = '"' + escapeStringForInfluxDB(h.hub.getDataValue("zwavePowerLevel")) + '"'
577587
def firmwareVersion = '"' + escapeStringForInfluxDB(h.firmwareVersionString) + '"'
@@ -588,6 +598,31 @@ def logSystemProperties() {
588598

589599
}
590600

601+
602+
/**
603+
* poolInfluxDB()
604+
**/
605+
def poolInfluxDB(data) {
606+
state.poolData = state.poolData + '\n' + "${data}"
607+
// If 80% of state data max commit immediately
608+
if (state.poolData.size() >= 80000) {
609+
commitPoolToInfluxDB()
610+
}
611+
else {
612+
runIn(5, commitPoolToInfluxDB)
613+
}
614+
}
615+
616+
/**
617+
* commit Pool
618+
**/
619+
def commitPoolToInfluxDB() {
620+
logger("commitPoolToInfluxDB(): ${state.poolData.size()} bytes", "info")
621+
logger("commitPoolToInfluxDB(): ${state.poolData}", "info")
622+
postToInfluxDB(state.poolData)
623+
state.poolData = ""
624+
}
625+
591626
/**
592627
* postToInfluxDB()
593628
*
@@ -597,7 +632,7 @@ def logSystemProperties() {
597632
**/
598633
def postToInfluxDB(data) {
599634
logger("postToInfluxDB(): Posting data to InfluxDB: Host: ${state.databaseHost}, Port: ${state.databasePort}, Database: ${state.databaseName}, Data: [${data}]","debug")
600-
635+
601636
try {
602637
def hubAction = new physicalgraph.device.HubAction(
603638
[
@@ -609,15 +644,15 @@ def postToInfluxDB(data) {
609644
null,
610645
[ callback: handleInfluxResponse ]
611646
)
612-
647+
613648
sendHubCommand(hubAction)
614649
}
615650
catch (Exception e) {
616651
logger("postToInfluxDB(): Exception ${e} on ${hubAction}","error")
617652
}
618653

619654
// For reference, code that could be used for WAN hosts:
620-
// def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
655+
// def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
621656
// try {
622657
// httpPost(url, data) { response ->
623658
// if (response.status != 999 ) {
@@ -626,7 +661,7 @@ def postToInfluxDB(data) {
626661
// log.debug "Response contentType: ${response.contentType}"
627662
// }
628663
// }
629-
// } catch (e) {
664+
// } catch (e) {
630665
// logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
631666
// }
632667
}
@@ -649,8 +684,8 @@ def handleInfluxResponse(physicalgraph.device.HubResponse hubResponse) {
649684

650685
/**
651686
* manageSchedules()
652-
*
653-
* Configures/restarts scheduled tasks:
687+
*
688+
* Configures/restarts scheduled tasks:
654689
* softPoll() - Run every {state.softPollingInterval} minutes.
655690
**/
656691
private manageSchedules() {
@@ -659,7 +694,7 @@ private manageSchedules() {
659694
// Generate a random offset (1-60):
660695
Random rand = new Random(now())
661696
def randomOffset = 0
662-
697+
663698
// softPoll:
664699
try {
665700
unschedule(softPoll)
@@ -673,26 +708,26 @@ private manageSchedules() {
673708
logger("manageSchedules(): Scheduling softpoll to run every ${state.softPollingInterval} minutes (offset of ${randomOffset} seconds).","trace")
674709
schedule("${randomOffset} 0/${state.softPollingInterval} * * * ?", "softPoll")
675710
}
676-
711+
677712
}
678713

679714
/**
680715
* manageSubscriptions()
681-
*
716+
*
682717
* Configures subscriptions.
683718
**/
684719
private manageSubscriptions() {
685720
logger("manageSubscriptions()","trace")
686721

687722
// Unsubscribe:
688723
unsubscribe()
689-
724+
690725
// Subscribe to App Touch events:
691726
subscribe(app,handleAppTouch)
692-
727+
693728
// Subscribe to mode events:
694729
if (prefLogModeEvents) subscribe(location, "mode", handleModeEvent)
695-
730+
696731
// Subscribe to device attributes (iterate over each attribute for each device collection in state.deviceAttributes):
697732
def devs // dynamic variable holding device collection.
698733
state.deviceAttributes.each { da ->
@@ -754,9 +789,9 @@ private encodeCredentialsBasic(username, password) {
754789
* escapeStringForInfluxDB()
755790
*
756791
* 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.
792+
*
793+
* If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must
794+
* be escaped using the backslash character \. Backslash characters do not need to be escaped.
760795
* Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
761796
*
762797
* Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
@@ -779,10 +814,10 @@ private escapeStringForInfluxDB(str) {
779814
* getGroupName()
780815
*
781816
* Get the name of a 'Group' (i.e. Room) from its ID.
782-
*
817+
*
783818
* This is done manually as there does not appear to be a way to enumerate
784819
* groups from a SmartApp currently.
785-
*
820+
*
786821
* GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
787822
*
788823
* See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
@@ -793,5 +828,6 @@ private getGroupName(id) {
793828
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Kitchen'}
794829
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Lounge'}
795830
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Office'}
796-
else {return 'Unknown'}
831+
else {return 'Unknown'}
797832
}
833+

0 commit comments

Comments
 (0)