@@ -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 {
137138def 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 **/
245250def handleAppTouch (evt ) {
246251 logger(" handleAppTouch()" ," trace" )
247-
252+
248253 softPoll()
249254}
250255
251256/**
252257 * handleModeEvent(evt)
253- *
258+ *
254259 * Log Mode changes.
255260 **/
256261def 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 **/
278283def 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 **/
506516def 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 **/
598633def 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 **/
656691private 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 **/
684719private 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