@@ -67,18 +67,19 @@ preferences {
67
67
input " prefDatabaseName" , " text" , title : " Database Name" , defaultValue : " " , required : true
68
68
input " prefDatabaseUser" , " text" , title : " Username" , required : false
69
69
input " prefDatabasePass" , " text" , title : " Password" , required : false
70
+ input " preDatabasePooling" , " bool" , title : " Enable Pooling" , defaultValue : true , required : true
70
71
}
71
-
72
+
72
73
section(" Polling:" ) {
73
74
input " prefSoftPollingInterval" , " number" , title :" Soft-Polling interval (minutes)" , defaultValue : 10 , required : true
74
75
}
75
-
76
+
76
77
section(" System Monitoring:" ) {
77
78
input " prefLogModeEvents" , " bool" , title :" Log Mode Events?" , defaultValue : true , required : true
78
79
input " prefLogHubProperties" , " bool" , title :" Log Hub Properties?" , defaultValue : true , required : true
79
80
input " prefLogLocationProperties" , " bool" , title :" Log Location Properties?" , defaultValue : true , required : true
80
81
}
81
-
82
+
82
83
section(" Devices To Monitor:" ) {
83
84
input " accelerometers" , " capability.accelerationSensor" , title : " Accelerometers" , multiple : true , required : false
84
85
input " alarms" , " capability.alarm" , title : " Alarms" , multiple : true , required : false
@@ -107,8 +108,8 @@ preferences {
107
108
input " sleepSensors" , " capability.sleepSensor" , title : " Sleep Sensors" , multiple : true , required : false
108
109
input " smokeDetectors" , " capability.smokeDetector" , title : " Smoke Detectors" , multiple : true , required : false
109
110
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
112
113
input " switchLevels" , " capability.switchLevel" , title : " Switch Levels" , multiple : true , required : false
113
114
input " tamperAlerts" , " capability.tamperAlert" , title : " Tamper Alerts" , multiple : true , required : false
114
115
input " temperatures" , " capability.temperatureMeasurement" , title : " Temperature Sensors" , multiple : true , required : false
@@ -137,7 +138,7 @@ preferences {
137
138
def installed () {
138
139
state. installedAt = now()
139
140
state. loggingLevelIDE = 5
140
- log. debug " ${ app.label} : Installed with settings: ${ settings} "
141
+ log. debug " ${ app.label} : Installed with settings: ${ settings} "
141
142
}
142
143
143
144
/**
@@ -151,11 +152,11 @@ def uninstalled() {
151
152
152
153
/**
153
154
* updated()
154
- *
155
+ *
155
156
* Runs when app settings are changed.
156
- *
157
+ *
157
158
* 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
159
160
* (used by manageSubscriptions() and softPoll()).
160
161
* Refreshes scheduling and subscriptions.
161
162
**/
@@ -164,24 +165,25 @@ def updated() {
164
165
165
166
// Update internal state:
166
167
state. loggingLevelIDE = (settings. configLoggingLevelIDE) ? settings. configLoggingLevelIDE. toInteger() : 3
167
-
168
+
168
169
// Database config:
169
170
state. databaseHost = settings. prefDatabaseHost
170
171
state. databasePort = settings. prefDatabasePort
171
172
state. databaseName = settings. prefDatabaseName
172
173
state. databaseUser = settings. prefDatabaseUser
173
- state. databasePass = settings. prefDatabasePass
174
-
174
+ state. databasePass = settings. prefDatabasePass
175
+ state. databasePool = settings. prefDatabasePooling
176
+
175
177
state. path = " /write?db=${ state.databaseName} "
176
- state. headers = [:]
178
+ state. headers = [:]
177
179
state. headers. put(" HOST" , " ${ state.databaseHost} :${ state.databasePort} " )
178
180
state. headers. put(" Content-Type" , " application/x-www-form-urlencoded" )
179
181
if (state. databaseUser && state. databasePass) {
180
182
state. headers. put(" Authorization" , encodeCredentialsBasic(state. databaseUser, state. databasePass))
181
183
}
182
184
183
185
// 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
185
187
// objects causes major issues (possibly memory issues?).
186
188
state. deviceAttributes = []
187
189
state. deviceAttributes << [ devices : ' accelerometers' , attributes : [' acceleration' ]]
@@ -211,8 +213,8 @@ def updated() {
211
213
state. deviceAttributes << [ devices : ' sleepSensors' , attributes : [' sleeping' ]]
212
214
state. deviceAttributes << [ devices : ' smokeDetectors' , attributes : [' smoke' ]]
213
215
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' ]]
216
218
state. deviceAttributes << [ devices : ' switchLevels' , attributes : [' level' ]]
217
219
state. deviceAttributes << [ devices : ' tamperAlerts' , attributes : [' tamper' ]]
218
220
state. deviceAttributes << [ devices : ' temperatures' , attributes : [' temperature' ]]
@@ -228,7 +230,10 @@ def updated() {
228
230
// Configure Scheduling:
229
231
state. softPollingInterval = settings. prefSoftPollingInterval. toInteger()
230
232
manageSchedules()
231
-
233
+
234
+ // Configure if we're pooling InfluxPooling
235
+ state. poolData = " "
236
+
232
237
// Configure Subscriptions:
233
238
manageSubscriptions()
234
239
}
@@ -239,18 +244,18 @@ def updated() {
239
244
240
245
/**
241
246
* handleAppTouch(evt)
242
- *
247
+ *
243
248
* Used for testing.
244
249
**/
245
250
def handleAppTouch (evt ) {
246
251
logger(" handleAppTouch()" ," trace" )
247
-
252
+
248
253
softPoll()
249
254
}
250
255
251
256
/**
252
257
* handleModeEvent(evt)
253
- *
258
+ *
254
259
* Log Mode changes.
255
260
**/
256
261
def handleModeEvent (evt ) {
@@ -268,16 +273,16 @@ def handleModeEvent(evt) {
268
273
*
269
274
* Builds data to send to InfluxDB.
270
275
* - 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
272
277
* represented as binary values (e.g. contact: closed = 1, open = 0)
273
- *
274
- * Useful references:
278
+ *
279
+ * Useful references:
275
280
* - http://docs.smartthings.com/en/latest/capabilities-reference.html
276
281
* - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
277
282
**/
278
283
def handleEvent (evt ) {
279
284
logger(" handleEvent(): $evt . displayName ($evt . name :$evt . unit ) $evt . value " ," info" )
280
-
285
+
281
286
// Build data string to send to InfluxDB:
282
287
// Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
283
288
// If value is an integer, it must have a trailing "i"
@@ -297,9 +302,9 @@ def handleEvent(evt) {
297
302
def unit = escapeStringForInfluxDB(evt. unit)
298
303
def value = escapeStringForInfluxDB(evt. value)
299
304
def valueBinary = ' '
300
-
305
+
301
306
def data = " ${ measurement} ,deviceId=${ deviceId} ,deviceName=${ deviceName} ,groupId=${ groupId} ,groupName=${ groupName} ,hubId=${ hubId} ,hubName=${ hubName} ,locationId=${ locationId} ,locationName=${ locationName} "
302
-
307
+
303
308
// Unit tag and fields depend on the event type:
304
309
// Most string-valued attributes can be translated to a binary value too.
305
310
if (' acceleration' == evt. name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
@@ -474,17 +479,22 @@ def handleEvent(evt) {
474
479
}
475
480
// Catch any other event with a string value that hasn't been handled:
476
481
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" )
478
483
value = ' "' + value + ' "'
479
484
data + = " ,unit=${ unit} value=${ value} "
480
485
}
481
486
// Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
482
487
else {
483
488
data + = " ,unit=${ unit} value=${ value} "
484
489
}
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
+ }
488
498
489
499
}
490
500
@@ -497,17 +507,17 @@ def handleEvent(evt) {
497
507
* softPoll()
498
508
*
499
509
* Executed by schedule.
500
- *
510
+ *
501
511
* Forces data to be posted to InfluxDB (even if an event has not been triggered).
502
512
* Doesn't poll devices, just builds a fake event to pass to handleEvent().
503
513
*
504
514
* Also calls LogSystemProperties().
505
515
**/
506
516
def softPoll () {
507
517
logger(" softPoll()" ," trace" )
508
-
518
+
509
519
logSystemProperties()
510
-
520
+
511
521
// Iterate over each attribute for each device, in each device collection in deviceAttributes:
512
522
def devs // temp variable to hold device collection.
513
523
state. deviceAttributes. each { da ->
@@ -519,7 +529,7 @@ def softPoll() {
519
529
logger(" softPoll(): Softpolling device ${ d} for attribute: ${ attr} " ," info" )
520
530
// Send fake event to handleEvent():
521
531
handleEvent([
522
- name : attr,
532
+ name : attr,
523
533
value : d. latestState(attr)?. value,
524
534
unit : d. latestState(attr)?. unit,
525
535
device : d,
@@ -571,7 +581,7 @@ def logSystemProperties() {
571
581
def hubIP = ' "' + escapeStringForInfluxDB(h. localIP) + ' "'
572
582
def hubStatus = ' "' + escapeStringForInfluxDB(h. status) + ' "'
573
583
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 "
575
585
def zigbeePowerLevel = h. hub. getDataValue(" zigbeePowerLevel" ) + ' i'
576
586
def zwavePowerLevel = ' "' + escapeStringForInfluxDB(h. hub. getDataValue(" zwavePowerLevel" )) + ' "'
577
587
def firmwareVersion = ' "' + escapeStringForInfluxDB(h. firmwareVersionString) + ' "'
@@ -588,6 +598,31 @@ def logSystemProperties() {
588
598
589
599
}
590
600
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
+
591
626
/**
592
627
* postToInfluxDB()
593
628
*
@@ -597,7 +632,7 @@ def logSystemProperties() {
597
632
**/
598
633
def postToInfluxDB (data ) {
599
634
logger(" postToInfluxDB(): Posting data to InfluxDB: Host: ${ state.databaseHost} , Port: ${ state.databasePort} , Database: ${ state.databaseName} , Data: [${ data} ]" ," debug" )
600
-
635
+
601
636
try {
602
637
def hubAction = new physicalgraph.device.HubAction (
603
638
[
@@ -609,15 +644,15 @@ def postToInfluxDB(data) {
609
644
null ,
610
645
[ callback : handleInfluxResponse ]
611
646
)
612
-
647
+
613
648
sendHubCommand(hubAction)
614
649
}
615
650
catch (Exception e) {
616
651
logger(" postToInfluxDB(): Exception ${ e} on ${ hubAction} " ," error" )
617
652
}
618
653
619
654
// 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}"
621
656
// try {
622
657
// httpPost(url, data) { response ->
623
658
// if (response.status != 999 ) {
@@ -626,7 +661,7 @@ def postToInfluxDB(data) {
626
661
// log.debug "Response contentType: ${response.contentType}"
627
662
// }
628
663
// }
629
- // } catch (e) {
664
+ // } catch (e) {
630
665
// logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
631
666
// }
632
667
}
@@ -649,8 +684,8 @@ def handleInfluxResponse(physicalgraph.device.HubResponse hubResponse) {
649
684
650
685
/**
651
686
* manageSchedules()
652
- *
653
- * Configures/restarts scheduled tasks:
687
+ *
688
+ * Configures/restarts scheduled tasks:
654
689
* softPoll() - Run every {state.softPollingInterval} minutes.
655
690
**/
656
691
private manageSchedules () {
@@ -659,7 +694,7 @@ private manageSchedules() {
659
694
// Generate a random offset (1-60):
660
695
Random rand = new Random (now())
661
696
def randomOffset = 0
662
-
697
+
663
698
// softPoll:
664
699
try {
665
700
unschedule(softPoll)
@@ -673,26 +708,26 @@ private manageSchedules() {
673
708
logger(" manageSchedules(): Scheduling softpoll to run every ${ state.softPollingInterval} minutes (offset of ${ randomOffset} seconds)." ," trace" )
674
709
schedule(" ${ randomOffset} 0/${ state.softPollingInterval} * * * ?" , " softPoll" )
675
710
}
676
-
711
+
677
712
}
678
713
679
714
/**
680
715
* manageSubscriptions()
681
- *
716
+ *
682
717
* Configures subscriptions.
683
718
**/
684
719
private manageSubscriptions () {
685
720
logger(" manageSubscriptions()" ," trace" )
686
721
687
722
// Unsubscribe:
688
723
unsubscribe()
689
-
724
+
690
725
// Subscribe to App Touch events:
691
726
subscribe(app,handleAppTouch)
692
-
727
+
693
728
// Subscribe to mode events:
694
729
if (prefLogModeEvents) subscribe(location, " mode" , handleModeEvent)
695
-
730
+
696
731
// Subscribe to device attributes (iterate over each attribute for each device collection in state.deviceAttributes):
697
732
def devs // dynamic variable holding device collection.
698
733
state. deviceAttributes. each { da ->
@@ -754,9 +789,9 @@ private encodeCredentialsBasic(username, password) {
754
789
* escapeStringForInfluxDB()
755
790
*
756
791
* 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.
760
795
* Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
761
796
*
762
797
* Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
@@ -779,10 +814,10 @@ private escapeStringForInfluxDB(str) {
779
814
* getGroupName()
780
815
*
781
816
* Get the name of a 'Group' (i.e. Room) from its ID.
782
- *
817
+ *
783
818
* This is done manually as there does not appear to be a way to enumerate
784
819
* groups from a SmartApp currently.
785
- *
820
+ *
786
821
* GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
787
822
*
788
823
* See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
@@ -793,5 +828,6 @@ private getGroupName(id) {
793
828
else if (id == ' XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' ) {return ' Kitchen' }
794
829
else if (id == ' XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' ) {return ' Lounge' }
795
830
else if (id == ' XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' ) {return ' Office' }
796
- else {return ' Unknown' }
831
+ else {return ' Unknown' }
797
832
}
833
+
0 commit comments