@@ -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 {
137137def 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 **/
245245def handleAppTouch (evt ) {
246246 logger(" handleAppTouch()" ," trace" )
247-
247+
248248 softPoll()
249249}
250250
251251/**
252252 * handleModeEvent(evt)
253- *
253+ *
254254 * Log Mode changes.
255255 **/
256256def 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 **/
278271def 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 **/
506510def 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 **/
598603def 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 **/
656661private 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 **/
684689private 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