Browse code

minor revisions

Gandolf authored on 07/09/2021 23:35:12
Showing 1 changed files
... ...
@@ -38,6 +38,8 @@
38 38
 #     status page and parsed the signal data from the html.
39 39
 #   * v23 released 11 Jun 2021 by J L Owrey; remove unused code.
40 40
 #   * v24 released 14 Jun 2021 by J L Owrey; minor revisions
41
+#   * v25 released 9 Jul 2021 by J L Owrey; improved handling of
42
+#         node status function
41 43
 #
42 44
 #2345678901234567890123456789012345678901234567890123456789012345678901234567890
43 45
 
... ...
@@ -59,7 +61,8 @@ _SERVER_MODE = "primary"
59 61
 
60 62
 # set url of the aredn node
61 63
 
62
-_DEFAULT_AREDN_NODE_URL = "{your AREDN mesh node url"
64
+_DEFAULT_AREDN_NODE_URL = \
65
+    "{your node url}"
63 66
 
64 67
     ### FILE AND FOLDER LOCATIONS ###
65 68
 
... ...
@@ -101,7 +104,7 @@ debugMode = False
101 104
 # count of failed attempts to get data from aredn node
102 105
 failedUpdateCount = 0
103 106
 # detected status of aredn node device
104
-nodeOnline = True
107
+nodeOnline = False
105 108
 
106 109
 # ip address of aredn node
107 110
 arednNodeUrl = _DEFAULT_AREDN_NODE_URL
... ...
@@ -165,10 +168,8 @@ def terminateAgentProcess(signal, frame):
165 168
            signal, frame - dummy parameters
166 169
        Returns: nothing
167 170
     """
168
-    # Inform downstream clients by removing output data file.
169
-    if os.path.exists(_OUTPUT_DATA_FILE):
170
-       os.remove(_OUTPUT_DATA_FILE)
171 171
     print('%s terminating arednsig agent process' % getTimeStamp())
172
+    setStatusToOffline()
172 173
     sys.exit(0)
173 174
 ##end def
174 175
 
... ...
@@ -183,12 +184,8 @@ def getNodeData(dData):
183 184
     """
184 185
     try:
185 186
         currentTime = time.time()
186
-
187 187
         response = urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
188
-
189
-        if verboseMode:
190
-            requestTime = time.time() - currentTime
191
-            print("http request: %.4f seconds" % requestTime)
188
+        requestTime = time.time() - currentTime
192 189
 
193 190
         content = response.read().decode('utf-8')
194 191
         content = content.replace('\n', '')
... ...
@@ -206,9 +203,10 @@ def getNodeData(dData):
206 203
 
207 204
     if debugMode:
208 205
         print(content)
206
+    if verboseMode:
207
+        print("http request successful: %.4f sec" % requestTime)
209 208
 
210 209
     dData['content'] = content
211
-
212 210
     return True
213 211
 ##end def
214 212
 
... ...
@@ -237,14 +235,19 @@ def parseDataString(dData):
237 235
         snr = snr.replace(' ','')
238 236
         lsnr = snr.split('/')
239 237
 
240
-        dData['time'] = getEpochSeconds(getTimeStamp())
241 238
         dData['signal'] = lsnr[0]
242 239
         dData['noise'] = lsnr[1]
243 240
         dData['snr'] = lsnr[2]
244
-    
245 241
     except Exception as exError:
246 242
         print("%s parse failed: %s" % (getTimeStamp(), exError))
247 243
         return False
244
+    ## end try
245
+
246
+    # Add status information to dictionary object.
247
+    dData['date'] = getTimeStamp()
248
+    dData['chartUpdateInterval'] = chartUpdateInterval
249
+    dData['dataRequestInterval'] = dataRequestInterval
250
+    dData['serverMode'] = _SERVER_MODE
248 251
 
249 252
     return True
250 253
 ##end def
... ...
@@ -262,16 +265,12 @@ def writeOutputFile(dData):
262 265
     # data items are sent to the client file.
263 266
     #    * The last database update date and time
264 267
     #    * The data request interval
265
-    lastUpdate = time.strftime( "%m.%d.%Y %T", 
266
-                                time.localtime(dData['time']) )
267 268
 
268 269
     # Format data into a JSON string.
269 270
     jsData = json.loads("{}")
270 271
     try:
271
-        jsData.update({"date": lastUpdate})
272
-        jsData.update({"chartUpdateInterval": chartUpdateInterval})
273
-        jsData.update({"dataRequestInterval": dataRequestInterval})
274
-        jsData.update({"serverMode": _SERVER_MODE})
272
+        for key in dData:
273
+            jsData.update({key:dData[key]})
275 274
         sData = "[%s]" % json.dumps(jsData)
276 275
     except Exception as exError:
277 276
         print("%s writeOutputFile: %s" % (getTimeStamp(), exError))
... ...
@@ -291,6 +290,35 @@ def writeOutputFile(dData):
291 290
     return True
292 291
 ## end def
293 292
 
293
+def setNodeStatus(updateSuccess):
294
+    """Detect if aredn node is offline or not available on
295
+       the network. After a set number of attempts to get data
296
+       from the node set a flag that the node is offline.
297
+       Parameters:
298
+           updateSuccess - a boolean that is True if data request
299
+                           successful, False otherwise
300
+       Returns: nothing
301
+    """
302
+    global failedUpdateCount, nodeOnline
303
+
304
+    if updateSuccess:
305
+        failedUpdateCount = 0
306
+        # Set status and send a message to the log if the device
307
+        # previously offline and is now online.
308
+        if not nodeOnline:
309
+            print('%s node online' % getTimeStamp())
310
+            nodeOnline = True
311
+        return
312
+    elif failedUpdateCount == _MAX_FAILED_DATA_REQUESTS - 1:
313
+        # Max number of failed data requests, so set
314
+        # device status to offline.
315
+        setStatusToOffline()
316
+    ## end if
317
+    failedUpdateCount += 1
318
+##end def
319
+
320
+    ### DATABASE FUNCTIONS ###
321
+
294 322
 def updateDatabase(dData):
295 323
     """
296 324
     Update the rrdtool database by executing an rrdtool system command.
... ...
@@ -300,9 +328,12 @@ def updateDatabase(dData):
300 328
                         written to the rr database file
301 329
     Returns: True if successful, False otherwise
302 330
     """
331
+    
332
+    time = getEpochSeconds(dData['date'])
333
+
303 334
     # Format the rrdtool update command.
304 335
     strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
305
-    strCmd = strFmt % (_RRD_FILE, dData['time'], dData['signal'], \
336
+    strCmd = strFmt % (_RRD_FILE, time, dData['signal'], \
306 337
              dData['noise'], dData['snr'], '0', \
307 338
              '0', '0', '0')
308 339
 
... ...
@@ -319,40 +350,11 @@ def updateDatabase(dData):
319 350
         return False
320 351
 
321 352
     if verboseMode and not debugMode:
322
-        print("database updated")
353
+        print("database update successful")
323 354
 
324 355
     return True
325 356
 ##end def
326 357
 
327
-def setNodeStatus(updateSuccess):
328
-    """Detect if aredn node is offline or not available on
329
-       the network. After a set number of attempts to get data
330
-       from the node set a flag that the node is offline.
331
-       Parameters:
332
-           updateSuccess - a boolean that is True if data request
333
-                           successful, False otherwise
334
-       Returns: nothing
335
-    """
336
-    global failedUpdateCount, nodeOnline
337
-
338
-    if updateSuccess:
339
-        failedUpdateCount = 0
340
-        # Set status and send a message to the log if the node was
341
-        # previously offline and is now online.
342
-        if not nodeOnline:
343
-            print('%s aredn node online' % getTimeStamp())
344
-            nodeOnline = True
345
-    else:
346
-        # The last attempt failed, so update the failed attempts
347
-        # count.
348
-        failedUpdateCount += 1
349
-
350
-    if failedUpdateCount > _MAX_FAILED_DATA_REQUESTS:
351
-        # Max number of failed data requests, so set
352
-        # node status to offline.
353
-        setStatusToOffline()
354
-##end def
355
-
356 358
 def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
357 359
                 lower, upper, addTrend, autoScale):
358 360
     """Uses rrdtool to create a graph of specified node data item.
... ...
@@ -513,6 +515,7 @@ def main():
513 515
     signal.signal(signal.SIGTERM, terminateAgentProcess)
514 516
     signal.signal(signal.SIGINT, terminateAgentProcess)
515 517
 
518
+    print('===================')
516 519
     print('%s starting up arednsig agent process' % \
517 520
                   (getTimeStamp()))
518 521
 
... ...
@@ -568,7 +571,7 @@ def main():
568 571
         if currentTime - lastChartUpdateTime > chartUpdateInterval:
569 572
             lastChartUpdateTime = currentTime
570 573
             p = multiprocessing.Process(target=generateGraphs, args=())
571
-            p.start()
574
+            #p.start()
572 575
 
573 576
         # Relinquish processing back to the operating system until
574 577
         # the next update interval.
... ...
@@ -576,9 +579,9 @@ def main():
576 579
         elapsedTime = time.time() - currentTime
577 580
         if verboseMode:
578 581
             if result:
579
-                print("update successful: %s sec" % elapsedTime)
582
+                print("update successful: %s sec\n" % elapsedTime)
580 583
             else:
581
-                print("update failed: %s sec" % elapsedTime)
584
+                print("update failed: %s sec\n" % elapsedTime)
582 585
         remainingTime = dataRequestInterval - elapsedTime
583 586
         if remainingTime > 0.0:
584 587
             time.sleep(remainingTime)
Browse code

minor revisions

Gandolf authored on 06/23/2021 00:43:14
Showing 1 changed files
... ...
@@ -59,7 +59,7 @@ _SERVER_MODE = "primary"
59 59
 
60 60
 # set url of the aredn node
61 61
 
62
-_DEFAULT_AREDN_NODE_URL = "http://localnode.local.mesh/cgi-bin/status"
62
+_DEFAULT_AREDN_NODE_URL = "{your AREDN mesh node url"
63 63
 
64 64
     ### FILE AND FOLDER LOCATIONS ###
65 65
 
... ...
@@ -92,8 +92,8 @@ _CHART_HEIGHT = 150
92 92
    ### GLOBAL VARIABLES ###
93 93
 
94 94
 # turn on or off of verbose debugging information
95
-debugOption = False
96
-verboseDebug = False
95
+verboseMode = False
96
+debugMode = False
97 97
 
98 98
 # The following two items are used for detecting system faults
99 99
 # and aredn node online or offline status.
... ...
@@ -174,19 +174,19 @@ def terminateAgentProcess(signal, frame):
174 174
 
175 175
   ###  PUBLIC METHODS  ###
176 176
 
177
-def getNodeData():
177
+def getNodeData(dData):
178 178
     """Send http request to aredn node.  The response from the
179 179
        node contains the node signal data as unformatted ascii text.
180 180
        Parameters: none
181
-       Returns: a string containing the node signal data if successful,
182
-                or None if not successful
181
+       Returns: True if successful,
182
+                or False if not successful
183 183
     """
184 184
     try:
185 185
         currentTime = time.time()
186 186
 
187 187
         response = urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
188 188
 
189
-        if debugOption:
189
+        if verboseMode:
190 190
             requestTime = time.time() - currentTime
191 191
             print("http request: %.4f seconds" % requestTime)
192 192
 
... ...
@@ -201,16 +201,18 @@ def getNodeData():
201 201
         # the device is down or unavailable over the network.  In
202 202
         # that case return None to the calling function.
203 203
         print("%s getNodeData: %s" % (getTimeStamp(), exError))
204
-        return None
204
+        return False
205 205
     ##end try
206 206
 
207
-    if verboseDebug:
207
+    if debugMode:
208 208
         print(content)
209
-   
210
-    return content
209
+
210
+    dData['content'] = content
211
+
212
+    return True
211 213
 ##end def
212 214
 
213
-def parseDataString(sData, dData):
215
+def parseDataString(dData):
214 216
     """Parse the node signal data JSON string from the aredn node
215 217
        into its component parts.  
216 218
        Parameters:
... ...
@@ -218,7 +220,7 @@ def parseDataString(sData, dData):
218 220
            dData - a dictionary object to contain the parsed data items
219 221
        Returns: True if successful, False otherwise
220 222
     """
221
- 
223
+    sData = dData.pop('content')
222 224
     try:
223 225
         strBeginSearch = '<nobr>Signal/Noise/Ratio</nobr></th>' \
224 226
                          '<td valign=middle><nobr><big><b>'
... ...
@@ -247,39 +249,6 @@ def parseDataString(sData, dData):
247 249
     return True
248 250
 ##end def
249 251
 
250
-def updateDatabase(dData):
251
-    """
252
-    Update the rrdtool database by executing an rrdtool system command.
253
-    Format the command using the data extracted from the aredn node
254
-    response.   
255
-    Parameters: dData - dictionary object containing data items to be
256
-                        written to the rr database file
257
-    Returns: True if successful, False otherwise
258
-    """
259
-    # Format the rrdtool update command.
260
-    strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
261
-    strCmd = strFmt % (_RRD_FILE, dData['time'], dData['signal'], \
262
-             dData['noise'], dData['snr'], '0', \
263
-             '0', '0', '0')
264
-
265
-    if verboseDebug:
266
-        print("%s" % strCmd) # DEBUG
267
-
268
-    # Run the command as a subprocess.
269
-    try:
270
-        subprocess.check_output(strCmd, shell=True,  \
271
-                             stderr=subprocess.STDOUT)
272
-    except subprocess.CalledProcessError as exError:
273
-        print("%s: rrdtool update failed: %s" % \
274
-                    (getTimeStamp(), exError.output))
275
-        return False
276
-
277
-    if debugOption and not verboseDebug:
278
-        print("database updated")
279
-
280
-    return True
281
-##end def
282
-
283 252
 def writeOutputFile(dData):
284 253
     """Write node data items to the output data file, formatted as 
285 254
        a Javascript file.  This file may then be accessed and used by
... ...
@@ -297,8 +266,8 @@ def writeOutputFile(dData):
297 266
                                 time.localtime(dData['time']) )
298 267
 
299 268
     # Format data into a JSON string.
269
+    jsData = json.loads("{}")
300 270
     try:
301
-        jsData = json.loads("{}")
302 271
         jsData.update({"date": lastUpdate})
303 272
         jsData.update({"chartUpdateInterval": chartUpdateInterval})
304 273
         jsData.update({"dataRequestInterval": dataRequestInterval})
... ...
@@ -308,7 +277,7 @@ def writeOutputFile(dData):
308 277
         print("%s writeOutputFile: %s" % (getTimeStamp(), exError))
309 278
         return False
310 279
 
311
-    if verboseDebug:
280
+    if debugMode:
312 281
         print(sData)
313 282
 
314 283
     try:
... ...
@@ -322,6 +291,39 @@ def writeOutputFile(dData):
322 291
     return True
323 292
 ## end def
324 293
 
294
+def updateDatabase(dData):
295
+    """
296
+    Update the rrdtool database by executing an rrdtool system command.
297
+    Format the command using the data extracted from the aredn node
298
+    response.   
299
+    Parameters: dData - dictionary object containing data items to be
300
+                        written to the rr database file
301
+    Returns: True if successful, False otherwise
302
+    """
303
+    # Format the rrdtool update command.
304
+    strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
305
+    strCmd = strFmt % (_RRD_FILE, dData['time'], dData['signal'], \
306
+             dData['noise'], dData['snr'], '0', \
307
+             '0', '0', '0')
308
+
309
+    if debugMode:
310
+        print("%s" % strCmd) # DEBUG
311
+
312
+    # Run the command as a subprocess.
313
+    try:
314
+        subprocess.check_output(strCmd, shell=True,  \
315
+                             stderr=subprocess.STDOUT)
316
+    except subprocess.CalledProcessError as exError:
317
+        print("%s: rrdtool update failed: %s" % \
318
+                    (getTimeStamp(), exError.output))
319
+        return False
320
+
321
+    if verboseMode and not debugMode:
322
+        print("database updated")
323
+
324
+    return True
325
+##end def
326
+
325 327
 def setNodeStatus(updateSuccess):
326 328
     """Detect if aredn node is offline or not available on
327 329
        the network. After a set number of attempts to get data
... ...
@@ -404,7 +406,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
404 406
         strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \
405 407
                   % trendWindow[gStart]
406 408
      
407
-    if verboseDebug:
409
+    if debugMode:
408 410
         print("%s" % strCmd) # DEBUG
409 411
     
410 412
     # Run the formatted rrdtool command as a subprocess.
... ...
@@ -416,7 +418,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
416 418
         print("rrdtool graph failed: %s" % (exError.output))
417 419
         return False
418 420
 
419
-    if debugOption:
421
+    if verboseMode:
420 422
         print("rrdtool graph: %s\n" % result.decode('utf-8'), end='')
421 423
     return True
422 424
 
... ...
@@ -458,7 +460,7 @@ def generateGraphs():
458 460
     createGraph('12m_snr', 'SNR', 'dB', 
459 461
                 'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
460 462
 
461
-    if debugOption:
463
+    if verboseMode:
462 464
         #print() # print a blank line to improve readability when in debug mode
463 465
         pass
464 466
 ##end def
... ...
@@ -471,16 +473,16 @@ def getCLarguments():
471 473
           -u sets the url of the aredn nodeing device
472 474
        Returns: nothing
473 475
     """
474
-    global debugOption, verboseDebug, dataRequestInterval, \
476
+    global verboseMode, debugMode, dataRequestInterval, \
475 477
            arednNodeUrl
476 478
 
477 479
     index = 1
478 480
     while index < len(sys.argv):
479
-        if sys.argv[index] == '-d':
480
-            debugOption = True
481
-        elif sys.argv[index] == '-v':
482
-            debugOption = True
483
-            verboseDebug = True
481
+        if sys.argv[index] == '-v':
482
+            verboseMode = True
483
+        elif sys.argv[index] == '-d':
484
+            verboseMode = True
485
+            debugMode = True
484 486
         elif sys.argv[index] == '-p':
485 487
             try:
486 488
                 dataRequestInterval = abs(int(sys.argv[index + 1]))
... ...
@@ -541,18 +543,13 @@ def main():
541 543
         if currentTime - lastDataRequestTime > dataRequestInterval:
542 544
             lastDataRequestTime = currentTime
543 545
             dData = {}
544
-            result = True
545 546
 
546 547
             # Get the data string from the device.
547
-            sData = getNodeData()
548
+            result = getNodeData(dData)
548 549
 
549
-            # If the first http request fails, try one more time.
550
-            if sData == None:
551
-                result = False
552
-            
553 550
             # If successful parse the data.
554 551
             if result:
555
-                result = parseDataString(sData, dData)
552
+                result = parseDataString(dData)
556 553
 
557 554
             # If parse successful, write data output data file.
558 555
             if result:
... ...
@@ -577,12 +574,11 @@ def main():
577 574
         # the next update interval.
578 575
 
579 576
         elapsedTime = time.time() - currentTime
580
-        if debugOption:
577
+        if verboseMode:
581 578
             if result:
582
-                print("%s update successful:" % getTimeStamp(), end='')
579
+                print("update successful: %s sec" % elapsedTime)
583 580
             else:
584
-                print("%s update failed:" % getTimeStamp(), end='')
585
-            print(" %6f seconds\n" % elapsedTime)
581
+                print("update failed: %s sec" % elapsedTime)
586 582
         remainingTime = dataRequestInterval - elapsedTime
587 583
         if remainingTime > 0.0:
588 584
             time.sleep(remainingTime)
Browse code

minor revisions

Gandolf authored on 06/21/2021 19:58:00
Showing 1 changed files
... ...
@@ -1,4 +1,4 @@
1
-#!/usr/bin/python2 -u
1
+#!/usr/bin/python3 -u
2 2
 # The -u option above turns off block buffering of python output. This 
3 3
 # assures that each error message gets individually printed to the log file.
4 4
 #
... ...
@@ -36,24 +36,30 @@
36 36
 #   * v22 released 31 Mar 2020 by J L Owrey; upgraded for compatibility with
37 37
 #     Aredn firmware version 3.20.3.0.  This agent now downloads the node's
38 38
 #     status page and parsed the signal data from the html.
39
+#   * v23 released 11 Jun 2021 by J L Owrey; remove unused code.
40
+#   * v24 released 14 Jun 2021 by J L Owrey; minor revisions
39 41
 #
40 42
 #2345678901234567890123456789012345678901234567890123456789012345678901234567890
41 43
 
42 44
 import os
43
-import urllib2
44 45
 import sys
45 46
 import signal
46 47
 import subprocess
47 48
 import multiprocessing
48 49
 import time
50
+import json
51
+from urllib.request import urlopen
52
+
53
+   ### ENVIRONMENT ###
49 54
 
50 55
 _USER = os.environ['USER']
56
+_SERVER_MODE = "primary"
51 57
 
52 58
    ### DEFAULT AREDN NODE URL ###
53 59
 
54 60
 # set url of the aredn node
55 61
 
56
-_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/status"
62
+_DEFAULT_AREDN_NODE_URL = "http://localnode.local.mesh/cgi-bin/status"
57 63
 
58 64
     ### FILE AND FOLDER LOCATIONS ###
59 65
 
... ...
@@ -62,30 +68,26 @@ _DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
62 68
 # folder for charts and output data file
63 69
 _CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
64 70
 # location of data output file
65
-_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
66
-# dummy output data file
67
-_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
71
+_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigData.js"
68 72
 # database that stores node data
69 73
 _RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
70 74
 
71 75
     ### GLOBAL CONSTANTS ###
72 76
 
77
+# max number of failed data requests allowed
78
+_MAX_FAILED_DATA_REQUESTS = 2
73 79
 # AREDN node data request interval in seconds
74 80
 _DEFAULT_DATA_REQUEST_INTERVAL = 60
81
+# number seconds to wait for a response to HTTP request
82
+_HTTP_REQUEST_TIMEOUT = 5
83
+
75 84
 # chart update interval in seconds
76 85
 _CHART_UPDATE_INTERVAL = 600
77
-
78
-# number seconds to wait for a response to HTTP request
79
-_HTTP_REQUEST_TIMEOUT = 10
80
-# max number of failed data requests allowed
81
-_MAX_FAILED_DATA_REQUESTS = 0
82 86
 # standard chart width in pixels
83 87
 _CHART_WIDTH = 600
84 88
 # standard chart height in pixels
85 89
 _CHART_HEIGHT = 150
86 90
 # Set this to True only if this server is intended to relay raw
87
-# node data to a mirror server.
88
-_RELAY_SERVER = False
89 91
 
90 92
    ### GLOBAL VARIABLES ###
91 93
 
... ...
@@ -107,8 +109,6 @@ arednNodeUrl = _DEFAULT_AREDN_NODE_URL
107 109
 dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
108 110
 # chart update interval
109 111
 chartUpdateInterval = _CHART_UPDATE_INTERVAL
110
-# last node request time
111
-lastDataPointTime = -1
112 112
 
113 113
   ###  PRIVATE METHODS  ###
114 114
 
... ...
@@ -122,7 +122,7 @@ def getTimeStamp():
122 122
 ##end def
123 123
 
124 124
 def getEpochSeconds(sTime):
125
-    """Convert the time stamp supplied in the weather data string
125
+    """Convert the time stamp supplied in the supplied string
126 126
        to seconds since 1/1/1970 00:00:00.
127 127
        Parameters: 
128 128
            sTime - the time stamp to be converted must be formatted
... ...
@@ -131,8 +131,8 @@ def getEpochSeconds(sTime):
131 131
     """
132 132
     try:
133 133
         t_sTime = time.strptime(sTime, '%m/%d/%Y %H:%M:%S')
134
-    except Exception, exError:
135
-        print '%s getEpochSeconds: %s' % (getTimeStamp(), exError)
134
+    except Exception as exError:
135
+        print('%s getEpochSeconds: %s' % (getTimeStamp(), exError))
136 136
         return None
137 137
     tSeconds = int(time.mktime(t_sTime))
138 138
     return tSeconds
... ...
@@ -150,12 +150,10 @@ def setStatusToOffline():
150 150
     # Inform downstream clients by removing output data file.
151 151
     if os.path.exists(_OUTPUT_DATA_FILE):
152 152
        os.remove(_OUTPUT_DATA_FILE)
153
-    if os.path.exists(_DUMMY_OUTPUT_FILE):
154
-       os.remove(_DUMMY_OUTPUT_FILE)
155 153
     # If the aredn node was previously online, then send
156 154
     # a message that we are now offline.
157 155
     if nodeOnline:
158
-        print '%s aredn node offline' % getTimeStamp()
156
+        print('%s aredn node offline' % getTimeStamp())
159 157
     nodeOnline = False
160 158
 ##end def
161 159
 
... ...
@@ -170,16 +168,13 @@ def terminateAgentProcess(signal, frame):
170 168
     # Inform downstream clients by removing output data file.
171 169
     if os.path.exists(_OUTPUT_DATA_FILE):
172 170
        os.remove(_OUTPUT_DATA_FILE)
173
-    if os.path.exists(_DUMMY_OUTPUT_FILE):
174
-       os.remove(_DUMMY_OUTPUT_FILE)
175
-    print '%s terminating arednsig agent process' % \
176
-              (getTimeStamp())
171
+    print('%s terminating arednsig agent process' % getTimeStamp())
177 172
     sys.exit(0)
178 173
 ##end def
179 174
 
180 175
   ###  PUBLIC METHODS  ###
181 176
 
182
-def getArednNodeData():
177
+def getNodeData():
183 178
     """Send http request to aredn node.  The response from the
184 179
        node contains the node signal data as unformatted ascii text.
185 180
        Parameters: none
... ...
@@ -187,44 +182,51 @@ def getArednNodeData():
187 182
                 or None if not successful
188 183
     """
189 184
     try:
190
-        conn = urllib2.urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
185
+        currentTime = time.time()
191 186
 
192
-        # Format received data into a single string.
193
-        content = ""
194
-        for line in conn:
195
-            content += line.strip()
196
-        del conn
187
+        response = urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
197 188
 
198
-    except Exception, exError:
189
+        if debugOption:
190
+            requestTime = time.time() - currentTime
191
+            print("http request: %.4f seconds" % requestTime)
192
+
193
+        content = response.read().decode('utf-8')
194
+        content = content.replace('\n', '')
195
+        content = content.replace('\r', '')
196
+        if content == "":
197
+            raise Exception("empty response")
198
+        
199
+    except Exception as exError:
199 200
         # If no response is received from the device, then assume that
200 201
         # the device is down or unavailable over the network.  In
201 202
         # that case return None to the calling function.
202
-        print "%s http error: %s" % (getTimeStamp(), exError)
203
+        print("%s getNodeData: %s" % (getTimeStamp(), exError))
203 204
         return None
205
+    ##end try
204 206
 
205 207
     if verboseDebug:
206
-        print "http request successful: %d bytes" % len(content)
207
-
208
+        print(content)
209
+   
208 210
     return content
209 211
 ##end def
210 212
 
211
-def parseNodeData(sData, dData):
212
-    """Parse the node  signal data JSON string from the aredn node
213
+def parseDataString(sData, dData):
214
+    """Parse the node signal data JSON string from the aredn node
213 215
        into its component parts.  
214 216
        Parameters:
215 217
            sData - the string containing the data to be parsed
216 218
            dData - a dictionary object to contain the parsed data items
217 219
        Returns: True if successful, False otherwise
218 220
     """
219
-    
220
-
221
+ 
221 222
     try:
222 223
         strBeginSearch = '<nobr>Signal/Noise/Ratio</nobr></th>' \
223 224
                          '<td valign=middle><nobr><big><b>'
224
-        strEndSearch = 'dB'
225
+        strEndSearch = 'dB</b>'
225 226
 
226 227
         iBeginIndex = sData.find(strBeginSearch) + len(strBeginSearch)
227 228
         iEndIndex = sData.find(strEndSearch, iBeginIndex)
229
+        #print("search params: %d, %d" % (iBeginIndex, iEndIndex))
228 230
 
229 231
         if iBeginIndex == -1 or iEndIndex == -1:
230 232
             raise Exception("signal data not found in status page")
... ...
@@ -234,17 +236,14 @@ def parseNodeData(sData, dData):
234 236
         lsnr = snr.split('/')
235 237
 
236 238
         dData['time'] = getEpochSeconds(getTimeStamp())
237
-
238 239
         dData['signal'] = lsnr[0]
239 240
         dData['noise'] = lsnr[1]
240 241
         dData['snr'] = lsnr[2]
241 242
     
242
-    except Exception, exError:
243
-        print "%s parse failed: %s" % (getTimeStamp(), exError)
243
+    except Exception as exError:
244
+        print("%s parse failed: %s" % (getTimeStamp(), exError))
244 245
         return False
245 246
 
246
-    if verboseDebug:
247
-        print "parse successful"
248 247
     return True
249 248
 ##end def
250 249
 
... ...
@@ -264,21 +263,24 @@ def updateDatabase(dData):
264 263
              '0', '0', '0')
265 264
 
266 265
     if verboseDebug:
267
-        print "%s" % strCmd # DEBUG
266
+        print("%s" % strCmd) # DEBUG
268 267
 
269 268
     # Run the command as a subprocess.
270 269
     try:
271 270
         subprocess.check_output(strCmd, shell=True,  \
272 271
                              stderr=subprocess.STDOUT)
273
-    except subprocess.CalledProcessError, exError:
274
-        print "%s: rrdtool update failed: %s" % \
275
-                    (getTimeStamp(), exError.output)
272
+    except subprocess.CalledProcessError as exError:
273
+        print("%s: rrdtool update failed: %s" % \
274
+                    (getTimeStamp(), exError.output))
276 275
         return False
277 276
 
277
+    if debugOption and not verboseDebug:
278
+        print("database updated")
279
+
278 280
     return True
279 281
 ##end def
280 282
 
281
-def writeOutputDataFile(sData, dData):
283
+def writeOutputFile(dData):
282 284
     """Write node data items to the output data file, formatted as 
283 285
        a Javascript file.  This file may then be accessed and used by
284 286
        by downstream clients, for instance, in HTML documents.
... ...
@@ -293,28 +295,29 @@ def writeOutputDataFile(sData, dData):
293 295
     #    * The data request interval
294 296
     lastUpdate = time.strftime( "%m.%d.%Y %T", 
295 297
                                 time.localtime(dData['time']) )
296
-    sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
297
-           (lastUpdate, chartUpdateInterval)
298
+
299
+    # Format data into a JSON string.
298 300
     try:
299
-        fc = open(_DUMMY_OUTPUT_FILE, "w")
300
-        fc.write(sDate)
301
-        fc.close()
302
-    except Exception, exError:
303
-        print "%s write node file failed: %s" % (getTimeStamp(), exError)
301
+        jsData = json.loads("{}")
302
+        jsData.update({"date": lastUpdate})
303
+        jsData.update({"chartUpdateInterval": chartUpdateInterval})
304
+        jsData.update({"dataRequestInterval": dataRequestInterval})
305
+        jsData.update({"serverMode": _SERVER_MODE})
306
+        sData = "[%s]" % json.dumps(jsData)
307
+    except Exception as exError:
308
+        print("%s writeOutputFile: %s" % (getTimeStamp(), exError))
304 309
         return False
305 310
 
306
-    if _RELAY_SERVER:
307
-        # Write the entire node data response to the output data file.
308
-        try:
309
-            fc = open(_OUTPUT_DATA_FILE, "w")
310
-            fc.write(sData)
311
-            fc.close()
312
-        except Exception, exError:
313
-            print "%s write output file failed: %s" % \
314
-                  (getTimeStamp(), exError)
315
-            return False
316
-        if verboseDebug:
317
-            print "write output data file: %d bytes" % len(sData)
311
+    if verboseDebug:
312
+        print(sData)
313
+
314
+    try:
315
+        fc = open(_OUTPUT_DATA_FILE, "w")
316
+        fc.write(sData)
317
+        fc.close()
318
+    except Exception as exError:
319
+        print("%s write output file failed: %s" % (getTimeStamp(), exError))
320
+        return False
318 321
 
319 322
     return True
320 323
 ## end def
... ...
@@ -335,7 +338,7 @@ def setNodeStatus(updateSuccess):
335 338
         # Set status and send a message to the log if the node was
336 339
         # previously offline and is now online.
337 340
         if not nodeOnline:
338
-            print '%s aredn node online' % getTimeStamp()
341
+            print('%s aredn node online' % getTimeStamp())
339 342
             nodeOnline = True
340 343
     else:
341 344
         # The last attempt failed, so update the failed attempts
... ...
@@ -394,27 +397,27 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
394 397
     if addTrend == 0:
395 398
         strCmd += "LINE1:dSeries#0400ff "
396 399
     elif addTrend == 1:
397
-        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
400
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \
398 401
                   % trendWindow[gStart]
399 402
     elif addTrend == 2:
400 403
         strCmd += "LINE1:dSeries#0400ff "
401
-        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
404
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \
402 405
                   % trendWindow[gStart]
403 406
      
404 407
     if verboseDebug:
405
-        print "%s" % strCmd # DEBUG
408
+        print("%s" % strCmd) # DEBUG
406 409
     
407 410
     # Run the formatted rrdtool command as a subprocess.
408 411
     try:
409 412
         result = subprocess.check_output(strCmd, \
410 413
                      stderr=subprocess.STDOUT,   \
411 414
                      shell=True)
412
-    except subprocess.CalledProcessError, exError:
413
-        print "rrdtool graph failed: %s" % (exError.output)
415
+    except subprocess.CalledProcessError as exError:
416
+        print("rrdtool graph failed: %s" % (exError.output))
414 417
         return False
415 418
 
416 419
     if debugOption:
417
-        print "rrdtool graph: %s\n" % result,
420
+        print("rrdtool graph: %s\n" % result.decode('utf-8'), end='')
418 421
     return True
419 422
 
420 423
 ##end def
... ...
@@ -456,7 +459,7 @@ def generateGraphs():
456 459
                 'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
457 460
 
458 461
     if debugOption:
459
-        #print # print a blank line to improve readability when in debug mode
462
+        #print() # print a blank line to improve readability when in debug mode
460 463
         pass
461 464
 ##end def
462 465
 
... ...
@@ -482,15 +485,17 @@ def getCLarguments():
482 485
             try:
483 486
                 dataRequestInterval = abs(int(sys.argv[index + 1]))
484 487
             except:
485
-                print "invalid polling period"
488
+                print("invalid polling period")
486 489
                 exit(-1)
487 490
             index += 1
488 491
         elif sys.argv[index] == '-u':
489 492
             arednNodeUrl = sys.argv[index + 1]
493
+            if arednNodeUrl.find('http://') < 0:
494
+                arednNodeUrl = 'http://' + arednNodeUrl
490 495
             index += 1
491 496
         else:
492 497
             cmd_name = sys.argv[0].split('/')
493
-            print "Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1]
498
+            print("Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1])
494 499
             exit(-1)
495 500
         index += 1
496 501
 ##end def
... ...
@@ -504,9 +509,10 @@ def main():
504 509
     global dataRequestInterval
505 510
 
506 511
     signal.signal(signal.SIGTERM, terminateAgentProcess)
512
+    signal.signal(signal.SIGINT, terminateAgentProcess)
507 513
 
508
-    print '%s starting up arednsig agent process' % \
509
-                  (getTimeStamp())
514
+    print('%s starting up arednsig agent process' % \
515
+                  (getTimeStamp()))
510 516
 
511 517
     # last time output JSON file updated
512 518
     lastDataRequestTime = -1
... ...
@@ -520,9 +526,9 @@ def main():
520 526
 
521 527
     ## Exit with error if rrdtool database does not exist.
522 528
     if not os.path.exists(_RRD_FILE):
523
-        print 'rrdtool database does not exist\n' \
529
+        print('rrdtool database does not exist\n' \
524 530
               'use createArednsigRrd script to ' \
525
-              'create rrdtool database\n'
531
+              'create rrdtool database\n')
526 532
         exit(1)
527 533
  
528 534
     ## main loop
... ...
@@ -538,21 +544,23 @@ def main():
538 544
             result = True
539 545
 
540 546
             # Get the data string from the device.
541
-            sData = getArednNodeData()
547
+            sData = getNodeData()
548
+
542 549
             # If the first http request fails, try one more time.
543 550
             if sData == None:
544 551
                 result = False
545
-
552
+            
546 553
             # If successful parse the data.
547 554
             if result:
548
-                result = parseNodeData(sData, dData)
549
-           
550
-            # If parse successful, write data to data files.
555
+                result = parseDataString(sData, dData)
556
+
557
+            # If parse successful, write data output data file.
551 558
             if result:
552
-                result = updateDatabase(dData)
559
+                writeOutputFile(dData)
553 560
 
561
+            # If write output file successful, update the database.
554 562
             if result:
555
-                writeOutputDataFile(sData, dData)
563
+                result = updateDatabase(dData)
556 564
 
557 565
             # Set the node status to online or offline depending on the
558 566
             # success or failure of the above operations.
... ...
@@ -571,10 +579,10 @@ def main():
571 579
         elapsedTime = time.time() - currentTime
572 580
         if debugOption:
573 581
             if result:
574
-                print "%s update successful:" % getTimeStamp(),
582
+                print("%s update successful:" % getTimeStamp(), end='')
575 583
             else:
576
-                print "%s update failed:" % getTimeStamp(),
577
-            print "%6f seconds processing time\n" % elapsedTime 
584
+                print("%s update failed:" % getTimeStamp(), end='')
585
+            print(" %6f seconds\n" % elapsedTime)
578 586
         remainingTime = dataRequestInterval - elapsedTime
579 587
         if remainingTime > 0.0:
580 588
             time.sleep(remainingTime)
... ...
@@ -583,8 +591,5 @@ def main():
583 591
 ## end def
584 592
 
585 593
 if __name__ == '__main__':
586
-    try:
587
-        main()
588
-    except KeyboardInterrupt:
589
-        print '\n',
590
-        terminateAgentProcess('KeyboardInterrupt','Module')
594
+    main()
595
+
Browse code

reorg_20202027

Gandolf authored on 10/27/2020 20:25:34
Showing 1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,590 @@
1
+#!/usr/bin/python2 -u
2
+# The -u option above turns off block buffering of python output. This 
3
+# assures that each error message gets individually printed to the log file.
4
+#
5
+# Module: arednsigAgent.py
6
+#
7
+# Description: This module acts as an agent between the aredn node
8
+# and aredn mest services.  The agent periodically sends an http
9
+# request to the aredn node, processes the response from
10
+# the node, and performs a number of operations:
11
+#     - conversion of data items
12
+#     - update a round robin (rrdtool) database with the node data
13
+#     - periodically generate graphic charts for display in html documents
14
+#     - write the processed node status to a JSON file for use by html
15
+#       documents
16
+#
17
+# Copyright 2020 Jeff Owrey
18
+#    This program is free software: you can redistribute it and/or modify
19
+#    it under the terms of the GNU General Public License as published by
20
+#    the Free Software Foundation, either version 3 of the License, or
21
+#    (at your option) any later version.
22
+#
23
+#    This program is distributed in the hope that it will be useful,
24
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
+#    GNU General Public License for more details.
27
+#
28
+#    You should have received a copy of the GNU General Public License
29
+#    along with this program.  If not, see http://www.gnu.org/license.
30
+#
31
+# Revision History
32
+#   * v20 released 11 Jan 2020 by J L Owrey; first release
33
+#   * v21 released 13 Feb 2020 by J L Owrey; fixed bug occuring when node
34
+#     powers on and signal data memory is empty.  Data points with N/A data
35
+#     are discarded.
36
+#   * v22 released 31 Mar 2020 by J L Owrey; upgraded for compatibility with
37
+#     Aredn firmware version 3.20.3.0.  This agent now downloads the node's
38
+#     status page and parsed the signal data from the html.
39
+#
40
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
41
+
42
+import os
43
+import urllib2
44
+import sys
45
+import signal
46
+import subprocess
47
+import multiprocessing
48
+import time
49
+
50
+_USER = os.environ['USER']
51
+
52
+   ### DEFAULT AREDN NODE URL ###
53
+
54
+# set url of the aredn node
55
+
56
+_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/status"
57
+
58
+    ### FILE AND FOLDER LOCATIONS ###
59
+
60
+# folder for containing dynamic data objects
61
+_DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
62
+# folder for charts and output data file
63
+_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
64
+# location of data output file
65
+_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
66
+# dummy output data file
67
+_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
68
+# database that stores node data
69
+_RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
70
+
71
+    ### GLOBAL CONSTANTS ###
72
+
73
+# AREDN node data request interval in seconds
74
+_DEFAULT_DATA_REQUEST_INTERVAL = 60
75
+# chart update interval in seconds
76
+_CHART_UPDATE_INTERVAL = 600
77
+
78
+# number seconds to wait for a response to HTTP request
79
+_HTTP_REQUEST_TIMEOUT = 10
80
+# max number of failed data requests allowed
81
+_MAX_FAILED_DATA_REQUESTS = 0
82
+# standard chart width in pixels
83
+_CHART_WIDTH = 600
84
+# standard chart height in pixels
85
+_CHART_HEIGHT = 150
86
+# Set this to True only if this server is intended to relay raw
87
+# node data to a mirror server.
88
+_RELAY_SERVER = False
89
+
90
+   ### GLOBAL VARIABLES ###
91
+
92
+# turn on or off of verbose debugging information
93
+debugOption = False
94
+verboseDebug = False
95
+
96
+# The following two items are used for detecting system faults
97
+# and aredn node online or offline status.
98
+
99
+# count of failed attempts to get data from aredn node
100
+failedUpdateCount = 0
101
+# detected status of aredn node device
102
+nodeOnline = True
103
+
104
+# ip address of aredn node
105
+arednNodeUrl = _DEFAULT_AREDN_NODE_URL
106
+# frequency of data requests to aredn node
107
+dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
108
+# chart update interval
109
+chartUpdateInterval = _CHART_UPDATE_INTERVAL
110
+# last node request time
111
+lastDataPointTime = -1
112
+
113
+  ###  PRIVATE METHODS  ###
114
+
115
+def getTimeStamp():
116
+    """
117
+    Set the error message time stamp to the local system time.
118
+    Parameters: none
119
+    Returns: string containing the time stamp
120
+    """
121
+    return time.strftime( "%m/%d/%Y %T", time.localtime() )
122
+##end def
123
+
124
+def getEpochSeconds(sTime):
125
+    """Convert the time stamp supplied in the weather data string
126
+       to seconds since 1/1/1970 00:00:00.
127
+       Parameters: 
128
+           sTime - the time stamp to be converted must be formatted
129
+                   as %m/%d/%Y %H:%M:%S
130
+       Returns: epoch seconds
131
+    """
132
+    try:
133
+        t_sTime = time.strptime(sTime, '%m/%d/%Y %H:%M:%S')
134
+    except Exception, exError:
135
+        print '%s getEpochSeconds: %s' % (getTimeStamp(), exError)
136
+        return None
137
+    tSeconds = int(time.mktime(t_sTime))
138
+    return tSeconds
139
+##end def
140
+
141
+def setStatusToOffline():
142
+    """Set the detected status of the aredn node to
143
+       "offline" and inform downstream clients by removing input
144
+       and output data files.
145
+       Parameters: none
146
+       Returns: nothing
147
+    """
148
+    global nodeOnline
149
+
150
+    # Inform downstream clients by removing output data file.
151
+    if os.path.exists(_OUTPUT_DATA_FILE):
152
+       os.remove(_OUTPUT_DATA_FILE)
153
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
154
+       os.remove(_DUMMY_OUTPUT_FILE)
155
+    # If the aredn node was previously online, then send
156
+    # a message that we are now offline.
157
+    if nodeOnline:
158
+        print '%s aredn node offline' % getTimeStamp()
159
+    nodeOnline = False
160
+##end def
161
+
162
+def terminateAgentProcess(signal, frame):
163
+    """Send a message to log when the agent process gets killed
164
+       by the operating system.  Inform downstream clients
165
+       by removing input and output data files.
166
+       Parameters:
167
+           signal, frame - dummy parameters
168
+       Returns: nothing
169
+    """
170
+    # Inform downstream clients by removing output data file.
171
+    if os.path.exists(_OUTPUT_DATA_FILE):
172
+       os.remove(_OUTPUT_DATA_FILE)
173
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
174
+       os.remove(_DUMMY_OUTPUT_FILE)
175
+    print '%s terminating arednsig agent process' % \
176
+              (getTimeStamp())
177
+    sys.exit(0)
178
+##end def
179
+
180
+  ###  PUBLIC METHODS  ###
181
+
182
+def getArednNodeData():
183
+    """Send http request to aredn node.  The response from the
184
+       node contains the node signal data as unformatted ascii text.
185
+       Parameters: none
186
+       Returns: a string containing the node signal data if successful,
187
+                or None if not successful
188
+    """
189
+    try:
190
+        conn = urllib2.urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
191
+
192
+        # Format received data into a single string.
193
+        content = ""
194
+        for line in conn:
195
+            content += line.strip()
196
+        del conn
197
+
198
+    except Exception, exError:
199
+        # If no response is received from the device, then assume that
200
+        # the device is down or unavailable over the network.  In
201
+        # that case return None to the calling function.
202
+        print "%s http error: %s" % (getTimeStamp(), exError)
203
+        return None
204
+
205
+    if verboseDebug:
206
+        print "http request successful: %d bytes" % len(content)
207
+
208
+    return content
209
+##end def
210
+
211
+def parseNodeData(sData, dData):
212
+    """Parse the node  signal data JSON string from the aredn node
213
+       into its component parts.  
214
+       Parameters:
215
+           sData - the string containing the data to be parsed
216
+           dData - a dictionary object to contain the parsed data items
217
+       Returns: True if successful, False otherwise
218
+    """
219
+    
220
+
221
+    try:
222
+        strBeginSearch = '<nobr>Signal/Noise/Ratio</nobr></th>' \
223
+                         '<td valign=middle><nobr><big><b>'
224
+        strEndSearch = 'dB'
225
+
226
+        iBeginIndex = sData.find(strBeginSearch) + len(strBeginSearch)
227
+        iEndIndex = sData.find(strEndSearch, iBeginIndex)
228
+
229
+        if iBeginIndex == -1 or iEndIndex == -1:
230
+            raise Exception("signal data not found in status page")
231
+
232
+        snr = sData[iBeginIndex:iEndIndex]
233
+        snr = snr.replace(' ','')
234
+        lsnr = snr.split('/')
235
+
236
+        dData['time'] = getEpochSeconds(getTimeStamp())
237
+
238
+        dData['signal'] = lsnr[0]
239
+        dData['noise'] = lsnr[1]
240
+        dData['snr'] = lsnr[2]
241
+    
242
+    except Exception, exError:
243
+        print "%s parse failed: %s" % (getTimeStamp(), exError)
244
+        return False
245
+
246
+    if verboseDebug:
247
+        print "parse successful"
248
+    return True
249
+##end def
250
+
251
+def updateDatabase(dData):
252
+    """
253
+    Update the rrdtool database by executing an rrdtool system command.
254
+    Format the command using the data extracted from the aredn node
255
+    response.   
256
+    Parameters: dData - dictionary object containing data items to be
257
+                        written to the rr database file
258
+    Returns: True if successful, False otherwise
259
+    """
260
+    # Format the rrdtool update command.
261
+    strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
262
+    strCmd = strFmt % (_RRD_FILE, dData['time'], dData['signal'], \
263
+             dData['noise'], dData['snr'], '0', \
264
+             '0', '0', '0')
265
+
266
+    if verboseDebug:
267
+        print "%s" % strCmd # DEBUG
268
+
269
+    # Run the command as a subprocess.
270
+    try:
271
+        subprocess.check_output(strCmd, shell=True,  \
272
+                             stderr=subprocess.STDOUT)
273
+    except subprocess.CalledProcessError, exError:
274
+        print "%s: rrdtool update failed: %s" % \
275
+                    (getTimeStamp(), exError.output)
276
+        return False
277
+
278
+    return True
279
+##end def
280
+
281
+def writeOutputDataFile(sData, dData):
282
+    """Write node data items to the output data file, formatted as 
283
+       a Javascript file.  This file may then be accessed and used by
284
+       by downstream clients, for instance, in HTML documents.
285
+       Parameters:
286
+           sData - a string object containing the data to be written
287
+                   to the output data file
288
+       Returns: True if successful, False otherwise
289
+    """
290
+    # Write file for use by html clients.  The following two
291
+    # data items are sent to the client file.
292
+    #    * The last database update date and time
293
+    #    * The data request interval
294
+    lastUpdate = time.strftime( "%m.%d.%Y %T", 
295
+                                time.localtime(dData['time']) )
296
+    sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
297
+           (lastUpdate, chartUpdateInterval)
298
+    try:
299
+        fc = open(_DUMMY_OUTPUT_FILE, "w")
300
+        fc.write(sDate)
301
+        fc.close()
302
+    except Exception, exError:
303
+        print "%s write node file failed: %s" % (getTimeStamp(), exError)
304
+        return False
305
+
306
+    if _RELAY_SERVER:
307
+        # Write the entire node data response to the output data file.
308
+        try:
309
+            fc = open(_OUTPUT_DATA_FILE, "w")
310
+            fc.write(sData)
311
+            fc.close()
312
+        except Exception, exError:
313
+            print "%s write output file failed: %s" % \
314
+                  (getTimeStamp(), exError)
315
+            return False
316
+        if verboseDebug:
317
+            print "write output data file: %d bytes" % len(sData)
318
+
319
+    return True
320
+## end def
321
+
322
+def setNodeStatus(updateSuccess):
323
+    """Detect if aredn node is offline or not available on
324
+       the network. After a set number of attempts to get data
325
+       from the node set a flag that the node is offline.
326
+       Parameters:
327
+           updateSuccess - a boolean that is True if data request
328
+                           successful, False otherwise
329
+       Returns: nothing
330
+    """
331
+    global failedUpdateCount, nodeOnline
332
+
333
+    if updateSuccess:
334
+        failedUpdateCount = 0
335
+        # Set status and send a message to the log if the node was
336
+        # previously offline and is now online.
337
+        if not nodeOnline:
338
+            print '%s aredn node online' % getTimeStamp()
339
+            nodeOnline = True
340
+    else:
341
+        # The last attempt failed, so update the failed attempts
342
+        # count.
343
+        failedUpdateCount += 1
344
+
345
+    if failedUpdateCount > _MAX_FAILED_DATA_REQUESTS:
346
+        # Max number of failed data requests, so set
347
+        # node status to offline.
348
+        setStatusToOffline()
349
+##end def
350
+
351
+def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
352
+                lower, upper, addTrend, autoScale):
353
+    """Uses rrdtool to create a graph of specified node data item.
354
+       Parameters:
355
+           fileName - name of file containing the graph
356
+           dataItem - data item to be graphed
357
+           gLabel - string containing a graph label for the data item
358
+           gTitle - string containing a title for the graph
359
+           gStart - beginning time of the graphed data
360
+           lower - lower bound for graph ordinate #NOT USED
361
+           upper - upper bound for graph ordinate #NOT USED
362
+           addTrend - 0, show only graph data
363
+                      1, show only a trend line
364
+                      2, show a trend line and the graph data
365
+           autoScale - if True, then use vertical axis auto scaling
366
+               (lower and upper parameters are ignored), otherwise use
367
+               lower and upper parameters to set vertical axis scale
368
+       Returns: True if successful, False otherwise
369
+    """
370
+    gPath = _CHARTS_DIRECTORY + fileName + ".png"
371
+    trendWindow = { 'end-1day': 7200,
372
+                    'end-4weeks': 172800,
373
+                    'end-12months': 604800 }
374
+ 
375
+    # Format the rrdtool graph command.
376
+
377
+    # Set chart start time, height, and width.
378
+    strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \
379
+             % (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT)
380
+   
381
+    # Set the range and scaling of the chart y-axis.
382
+    if lower < upper:
383
+        strCmd  +=  "-l %s -u %s -r " % (lower, upper)
384
+    elif autoScale:
385
+        strCmd += "-A "
386
+    strCmd += "-Y "
387
+
388
+    # Set the chart ordinate label and chart title. 
389
+    strCmd += "-v %s -t %s " % (gLabel, gTitle)
390
+ 
391
+    # Show the data, or a moving average trend line over
392
+    # the data, or both.
393
+    strCmd += "DEF:dSeries=%s:%s:LAST " % (_RRD_FILE, dataItem)
394
+    if addTrend == 0:
395
+        strCmd += "LINE1:dSeries#0400ff "
396
+    elif addTrend == 1:
397
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
398
+                  % trendWindow[gStart]
399
+    elif addTrend == 2:
400
+        strCmd += "LINE1:dSeries#0400ff "
401
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
402
+                  % trendWindow[gStart]
403
+     
404
+    if verboseDebug:
405
+        print "%s" % strCmd # DEBUG
406
+    
407
+    # Run the formatted rrdtool command as a subprocess.
408
+    try:
409
+        result = subprocess.check_output(strCmd, \
410
+                     stderr=subprocess.STDOUT,   \
411
+                     shell=True)
412
+    except subprocess.CalledProcessError, exError:
413
+        print "rrdtool graph failed: %s" % (exError.output)
414
+        return False
415
+
416
+    if debugOption:
417
+        print "rrdtool graph: %s\n" % result,
418
+    return True
419
+
420
+##end def
421
+
422
+def generateGraphs():
423
+    """Generate graphs for display in html documents.
424
+       Parameters: none
425
+       Returns: nothing
426
+    """
427
+    autoScale = False
428
+
429
+    # The following will force creation of charts
430
+    # of only signal strength and S/N charts.  Note that the following
431
+    # data items appear constant and do not show variation with time:
432
+    # noise level, rx mcs, rx rate, tx mcs, tx rate.  Therefore, until
433
+    # these parameters are demonstrated to vary in time, there is no point
434
+    # in creating the charts for these data items.
435
+    createAllCharts = False
436
+
437
+    # 24 hour stock charts
438
+
439
+    createGraph('24hr_signal', 'S', 'dBm', 
440
+                'RSSI\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
441
+    createGraph('24hr_snr', 'SNR', 'dB', 
442
+                'SNR\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
443
+
444
+    # 4 week stock charts
445
+
446
+    createGraph('4wk_signal', 'S', 'dBm', 
447
+                'RSSI\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
448
+    createGraph('4wk_snr', 'SNR', 'dB', 
449
+                'SNR\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
450
+
451
+    # 12 month stock charts
452
+
453
+    createGraph('12m_signal', 'S', 'dBm', 
454
+                'RSSI\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
455
+    createGraph('12m_snr', 'SNR', 'dB', 
456
+                'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
457
+
458
+    if debugOption:
459
+        #print # print a blank line to improve readability when in debug mode
460
+        pass
461
+##end def
462
+
463
+def getCLarguments():
464
+    """Get command line arguments.  There are four possible arguments
465
+          -d turns on debug mode
466
+          -v turns on verbose debug mode
467
+          -t sets the aredn node query interval
468
+          -u sets the url of the aredn nodeing device
469
+       Returns: nothing
470
+    """
471
+    global debugOption, verboseDebug, dataRequestInterval, \
472
+           arednNodeUrl
473
+
474
+    index = 1
475
+    while index < len(sys.argv):
476
+        if sys.argv[index] == '-d':
477
+            debugOption = True
478
+        elif sys.argv[index] == '-v':
479
+            debugOption = True
480
+            verboseDebug = True
481
+        elif sys.argv[index] == '-p':
482
+            try:
483
+                dataRequestInterval = abs(int(sys.argv[index + 1]))
484
+            except:
485
+                print "invalid polling period"
486
+                exit(-1)
487
+            index += 1
488
+        elif sys.argv[index] == '-u':
489
+            arednNodeUrl = sys.argv[index + 1]
490
+            index += 1
491
+        else:
492
+            cmd_name = sys.argv[0].split('/')
493
+            print "Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1]
494
+            exit(-1)
495
+        index += 1
496
+##end def
497
+
498
+def main():
499
+    """Handles timing of events and acts as executive routine managing
500
+       all other functions.
501
+       Parameters: none
502
+       Returns: nothing
503
+    """
504
+    global dataRequestInterval
505
+
506
+    signal.signal(signal.SIGTERM, terminateAgentProcess)
507
+
508
+    print '%s starting up arednsig agent process' % \
509
+                  (getTimeStamp())
510
+
511
+    # last time output JSON file updated
512
+    lastDataRequestTime = -1
513
+    # last time charts generated
514
+    lastChartUpdateTime = - 1
515
+    # last time the rrdtool database updated
516
+    lastDatabaseUpdateTime = -1
517
+
518
+    ## Get command line arguments.
519
+    getCLarguments()
520
+
521
+    ## Exit with error if rrdtool database does not exist.
522
+    if not os.path.exists(_RRD_FILE):
523
+        print 'rrdtool database does not exist\n' \
524
+              'use createArednsigRrd script to ' \
525
+              'create rrdtool database\n'
526
+        exit(1)
527
+ 
528
+    ## main loop
529
+    while True:
530
+
531
+        currentTime = time.time() # get current time in seconds
532
+
533
+        # Every web update interval request data from the aredn
534
+        # node and process the received data.
535
+        if currentTime - lastDataRequestTime > dataRequestInterval:
536
+            lastDataRequestTime = currentTime
537
+            dData = {}
538
+            result = True
539
+
540
+            # Get the data string from the device.
541
+            sData = getArednNodeData()
542
+            # If the first http request fails, try one more time.
543
+            if sData == None:
544
+                result = False
545
+
546
+            # If successful parse the data.
547
+            if result:
548
+                result = parseNodeData(sData, dData)
549
+           
550
+            # If parse successful, write data to data files.
551
+            if result:
552
+                result = updateDatabase(dData)
553
+
554
+            if result:
555
+                writeOutputDataFile(sData, dData)
556
+
557
+            # Set the node status to online or offline depending on the
558
+            # success or failure of the above operations.
559
+            setNodeStatus(result)
560
+
561
+
562
+        # At the chart generation interval, generate charts.
563
+        if currentTime - lastChartUpdateTime > chartUpdateInterval:
564
+            lastChartUpdateTime = currentTime
565
+            p = multiprocessing.Process(target=generateGraphs, args=())
566
+            p.start()
567
+
568
+        # Relinquish processing back to the operating system until
569
+        # the next update interval.
570
+
571
+        elapsedTime = time.time() - currentTime
572
+        if debugOption:
573
+            if result:
574
+                print "%s update successful:" % getTimeStamp(),
575
+            else:
576
+                print "%s update failed:" % getTimeStamp(),
577
+            print "%6f seconds processing time\n" % elapsedTime 
578
+        remainingTime = dataRequestInterval - elapsedTime
579
+        if remainingTime > 0.0:
580
+            time.sleep(remainingTime)
581
+    ## end while
582
+    return
583
+## end def
584
+
585
+if __name__ == '__main__':
586
+    try:
587
+        main()
588
+    except KeyboardInterrupt:
589
+        print '\n',
590
+        terminateAgentProcess('KeyboardInterrupt','Module')
Browse code

support for Aredn FW v3.20.3.0

gandolf authored on 03/31/2020 17:37:45
Showing 1 changed files
1 1
deleted file mode 100755
... ...
@@ -1,704 +0,0 @@
1
-#!/usr/bin/python -u
2
-# The -u option above turns off block buffering of python output. This 
3
-# assures that each error message gets individually printed to the log file.
4
-#
5
-# Module: arednsigAgent.py
6
-#
7
-# Description: This module acts as an agent between the aredn node
8
-# and aredn mest services.  The agent periodically sends an http
9
-# request to the aredn node, processes the response from
10
-# the node, and performs a number of operations:
11
-#     - conversion of data items
12
-#     - update a round robin (rrdtool) database with the node data
13
-#     - periodically generate graphic charts for display in html documents
14
-#     - write the processed node status to a JSON file for use by html
15
-#       documents
16
-#
17
-# Copyright 2020 Jeff Owrey
18
-#    This program is free software: you can redistribute it and/or modify
19
-#    it under the terms of the GNU General Public License as published by
20
-#    the Free Software Foundation, either version 3 of the License, or
21
-#    (at your option) any later version.
22
-#
23
-#    This program is distributed in the hope that it will be useful,
24
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
25
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
-#    GNU General Public License for more details.
27
-#
28
-#    You should have received a copy of the GNU General Public License
29
-#    along with this program.  If not, see http://www.gnu.org/license.
30
-#
31
-# Revision History
32
-#   * v20 released 11 Jan 2020 by J L Owrey; first release
33
-#   * v21 released 13 Feb 2020 by J L Owrey; fixed bug occuring when node
34
-#     powers on and signal data memory is empty.  Data points with N/A data
35
-#     are discarded.
36
-#
37
-#2345678901234567890123456789012345678901234567890123456789012345678901234567890
38
-
39
-import os
40
-import urllib2
41
-import sys
42
-import signal
43
-import subprocess
44
-import multiprocessing
45
-import time
46
-import json
47
-
48
-_USER = os.environ['USER']
49
-_HOSTNAME = os.uname()[1]
50
-
51
-   ### DEFAULT AREDN NODE URL ###
52
-
53
-# set url of the aredn node
54
-
55
-_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
56
-
57
-if _HOSTNAME == "raspi2": 
58
-    _DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
59
-
60
-    ### FILE AND FOLDER LOCATIONS ###
61
-
62
-# folder for containing dynamic data objects
63
-_DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
64
-# folder for charts and output data file
65
-_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
66
-# location of data output file
67
-_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
68
-# dummy output data file
69
-_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
70
-# database that stores node data
71
-_RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
72
-
73
-    ### GLOBAL CONSTANTS ###
74
-
75
-# max number of failed data requests allowed
76
-_MAX_FAILED_DATA_REQUESTS = 0
77
-# interval in minutes between data requests to the aredn node
78
-_DEFAULT_DATA_REQUEST_INTERVAL = 60
79
-# number seconds to wait for a response to HTTP request
80
-_HTTP_REQUEST_TIMEOUT = 10
81
-# standard chart width in pixels
82
-_CHART_WIDTH = 600
83
-# standard chart height in pixels
84
-_CHART_HEIGHT = 150
85
-# Set this to True only if this server is intended to relay raw
86
-# node data to a mirror server.
87
-_RELAY_SERVER = False
88
-
89
-   ### GLOBAL VARIABLES ###
90
-
91
-# turn on or off of verbose debugging information
92
-debugOption = False
93
-verboseDebug = False
94
-
95
-# The following two items are used for detecting system faults
96
-# and aredn node online or offline status.
97
-
98
-# count of failed attempts to get data from aredn node
99
-failedUpdateCount = 0
100
-# detected status of aredn node device
101
-nodeOnline = True
102
-
103
-# ip address of aredn node
104
-arednNodeUrl = _DEFAULT_AREDN_NODE_URL
105
-# frequency of data requests to aredn node
106
-dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
107
-# last node request time
108
-lastDataPointTime = -1
109
-
110
-  ###  PRIVATE METHODS  ###
111
-
112
-def getTimeStamp():
113
-    """
114
-    Set the error message time stamp to the local system time.
115
-    Parameters: none
116
-    Returns: string containing the time stamp
117
-    """
118
-    return time.strftime( "%m/%d/%Y %T", time.localtime() )
119
-##end def
120
-
121
-def getLastDataPointTime():
122
-    """
123
-    Get the timestamp of the most recent update to the round robin
124
-    database.
125
-    Parameters: none
126
-    Returns: string epoch time stamp of the last rrd update
127
-    """
128
-    strCmd = "rrdtool lastupdate %s" % \
129
-             (_RRD_FILE)
130
-
131
-    # Run the command as a subprocess.
132
-    try:
133
-        result = subprocess.check_output(strCmd, shell=True,  \
134
-                             stderr=subprocess.STDOUT)
135
-    except subprocess.CalledProcessError, exError:
136
-        print "%s: rrdtool update failed: %s" % \
137
-                    (getTimeStamp(), exError.output)
138
-        return None
139
-
140
-    # Return just the epoch time stamp of the last rrd update
141
-    return int((result.split('\n')[-2]).split(':')[0])
142
-##end def
143
-
144
-def setStatusToOffline():
145
-    """Set the detected status of the aredn node to
146
-       "offline" and inform downstream clients by removing input
147
-       and output data files.
148
-       Parameters: none
149
-       Returns: nothing
150
-    """
151
-    global nodeOnline
152
-
153
-    # Inform downstream clients by removing output data file.
154
-    if os.path.exists(_OUTPUT_DATA_FILE):
155
-       os.remove(_OUTPUT_DATA_FILE)
156
-    if os.path.exists(_DUMMY_OUTPUT_FILE):
157
-       os.remove(_DUMMY_OUTPUT_FILE)
158
-    # If the aredn node was previously online, then send
159
-    # a message that we are now offline.
160
-    if nodeOnline:
161
-        print '%s aredn node offline' % getTimeStamp()
162
-    nodeOnline = False
163
-##end def
164
-
165
-def terminateAgentProcess(signal, frame):
166
-    """Send a message to log when the agent process gets killed
167
-       by the operating system.  Inform downstream clients
168
-       by removing input and output data files.
169
-       Parameters:
170
-           signal, frame - dummy parameters
171
-       Returns: nothing
172
-    """
173
-    # Inform downstream clients by removing output data file.
174
-    if os.path.exists(_OUTPUT_DATA_FILE):
175
-       os.remove(_OUTPUT_DATA_FILE)
176
-    if os.path.exists(_DUMMY_OUTPUT_FILE):
177
-       os.remove(_DUMMY_OUTPUT_FILE)
178
-    print '%s terminating arednsig agent process' % \
179
-              (getTimeStamp())
180
-    sys.exit(0)
181
-##end def
182
-
183
-  ###  PUBLIC METHODS  ###
184
-
185
-def getArednNodeData():
186
-    """Send http request to aredn node.  The response from the
187
-       node contains the node signal data as unformatted ascii text.
188
-       Parameters: none
189
-       Returns: a string containing the node signal data if successful,
190
-                or None if not successful
191
-    """
192
-    try:
193
-        conn = urllib2.urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
194
-
195
-        # Format received data into a single string.
196
-        content = ""
197
-        for line in conn:
198
-            content += line.strip()
199
-        del conn
200
-
201
-    except Exception, exError:
202
-        # If no response is received from the device, then assume that
203
-        # the device is down or unavailable over the network.  In
204
-        # that case return None to the calling function.
205
-        print "%s http error: %s" % (getTimeStamp(), exError)
206
-        return None
207
-
208
-    if verboseDebug:
209
-        print "http request successful: %d bytes" % len(content)
210
-
211
-    return content
212
-##end def
213
-
214
-def parseNodeData(sData, ldData):
215
-    """Parse the node  signal data JSON string from the aredn node
216
-       into its component parts.  
217
-       Parameters:
218
-           sData - the string containing the data to be parsed
219
-           dData - a dictionary object to contain the parsed data items
220
-       Returns: True if successful, False otherwise
221
-    """
222
-    iTrail = int(dataRequestInterval)
223
-
224
-    try:
225
-        ldTmp = json.loads(sData[1:-1])
226
-        ldTmp = ldTmp[-iTrail:]
227
-        if len(ldTmp) != iTrail:
228
-            #raise Exception("truncated list")
229
-            pass
230
-    except Exception, exError:
231
-        print "%s parse failed: %s" % (getTimeStamp(), exError)
232
-        return False
233
-    
234
-    del ldData[:]
235
-    for item in ldTmp:
236
-        ldData.append(item)
237
-
238
-    if verboseDebug:
239
-        print "parse successful: %d data points" % len(ldData)
240
-    return True
241
-##end def
242
-
243
-def convertData(ldData):
244
-    """Convert individual node signal data items as necessary.
245
-       Parameters:
246
-           dData - a dictionary object containing the node signal data
247
-       Returns: True if successful, False otherwise
248
-    """
249
-    # parse example string
250
-    # {u'tx_mcs': u'15', u'rx_mcs': u'15', u'm': 47,
251
-    #  u'label': u'01/10/2020 22:17:01', u'rx_rate': u'130',
252
-    #  u'y': [-48, -95], u'x': 1578694621000, u'tx_rate': u'130'}
253
-    #
254
-    index = 0
255
-    while index < len(ldData):
256
-        item = ldData[index]
257
-        try:
258
-            item['time'] = int(item.pop('x')) / 1000
259
-            item['signal'] = int(item['y'][0])
260
-            item['noise'] = int(item['y'][1])
261
-            item['snr'] = int(item.pop('m'))
262
-            item['rx_mcs'] = int(item.pop('rx_mcs'))
263
-            item['tx_mcs'] = int(item.pop('tx_mcs'))
264
-            item['rx_rate'] = int(item.pop('rx_rate'))
265
-            item['tx_rate'] = int(item.pop('tx_rate'))
266
-            item.pop('y')
267
-            item.pop('label')
268
-        except Exception, exError:
269
-            print "%s convert data item failed: %s" % (getTimeStamp(), exError)
270
-            print "discarding %s" % item
271
-            #return False
272
-            ldData.pop(index)
273
-        else:
274
-            index += 1
275
-    ##end for
276
-
277
-    if len(ldData) > 0:
278
-        if verboseDebug:
279
-            print "convert data successful"
280
-        return True
281
-    else:
282
-        print "convert data failed"
283
-        return False
284
-##end def
285
-
286
-def updateDatabase(ldData):
287
-    """
288
-    Update the rrdtool database by executing an rrdtool system command.
289
-    Format the command using the data extracted from the aredn node
290
-    response.   
291
-    Parameters: dData - dictionary object containing data items to be
292
-                        written to the rr database file
293
-    Returns: True if successful, False otherwise
294
-    """
295
-    updateCount = 0
296
-    lastDataPointTime = getLastDataPointTime()
297
-
298
-    if verboseDebug:
299
-         print "updating database..."
300
-
301
-    for item in ldData:
302
-
303
-        if item['time'] <= lastDataPointTime:
304
-            if verboseDebug:
305
-                print "%s invalid timestamp: discarding data" % \
306
-                      (getTimeStamp())
307
-            continue
308
-
309
-        # Format the rrdtool update command.
310
-        strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
311
-        strCmd = strFmt % (_RRD_FILE, item['time'], item['signal'], \
312
-                 item['noise'], item['snr'], item['rx_mcs'], \
313
-                 item['tx_mcs'], item['rx_rate'], item['tx_rate'])
314
-
315
-        if verboseDebug:
316
-            print "%s" % strCmd # DEBUG
317
-
318
-        # Run the command as a subprocess.
319
-        try:
320
-            subprocess.check_output(strCmd, shell=True,  \
321
-                                 stderr=subprocess.STDOUT)
322
-        except subprocess.CalledProcessError, exError:
323
-            print "%s: rrdtool update failed: %s" % \
324
-                        (getTimeStamp(), exError.output)
325
-            return False
326
-        updateCount += 1
327
-    ##end for
328
-
329
-    if debugOption:
330
-        print '%s added %d data points to database' % \
331
-              (getTimeStamp(), updateCount)
332
-    return True
333
-##end def
334
-
335
-def writeOutputDataFile(sData, ldData):
336
-    """Write node data items to the output data file, formatted as 
337
-       a Javascript file.  This file may then be accessed and used by
338
-       by downstream clients, for instance, in HTML documents.
339
-       Parameters:
340
-           sData - a string object containing the data to be written
341
-                   to the output data file
342
-       Returns: True if successful, False otherwise
343
-    """
344
-    # Write file for use by html clients.  The following two
345
-    # data items are sent to the client file.
346
-    #    * The last database update date and time
347
-    #    * The data request interval
348
-    lastUpdate = time.strftime( "%m.%d.%Y %T", 
349
-                                time.localtime(ldData[-1]['time']) )
350
-    sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
351
-           (lastUpdate, dataRequestInterval)
352
-    try:
353
-        fc = open(_DUMMY_OUTPUT_FILE, "w")
354
-        fc.write(sDate)
355
-        fc.close()
356
-    except Exception, exError:
357
-        print "%s write node file failed: %s" % (getTimeStamp(), exError)
358
-        return False
359
-
360
-    if _RELAY_SERVER:
361
-        # Write the entire node data response to the output data file.
362
-        try:
363
-            fc = open(_OUTPUT_DATA_FILE, "w")
364
-            fc.write(sData)
365
-            fc.close()
366
-        except Exception, exError:
367
-            print "%s write output file failed: %s" % \
368
-                  (getTimeStamp(), exError)
369
-            return False
370
-        if verboseDebug:
371
-            print "write output data file: %d bytes" % len(sData)
372
-
373
-    return True
374
-## end def
375
-
376
-def setNodeStatus(updateSuccess):
377
-    """Detect if aredn node is offline or not available on
378
-       the network. After a set number of attempts to get data
379
-       from the node set a flag that the node is offline.
380
-       Parameters:
381
-           updateSuccess - a boolean that is True if data request
382
-                           successful, False otherwise
383
-       Returns: nothing
384
-    """
385
-    global failedUpdateCount, nodeOnline
386
-
387
-    if updateSuccess:
388
-        failedUpdateCount = 0
389
-        # Set status and send a message to the log if the node was
390
-        # previously offline and is now online.
391
-        if not nodeOnline:
392
-            print '%s aredn node online' % getTimeStamp()
393
-            nodeOnline = True
394
-    else:
395
-        # The last attempt failed, so update the failed attempts
396
-        # count.
397
-        failedUpdateCount += 1
398
-
399
-    if failedUpdateCount > _MAX_FAILED_DATA_REQUESTS:
400
-        # Max number of failed data requests, so set
401
-        # node status to offline.
402
-        setStatusToOffline()
403
-##end def
404
-
405
-def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
406
-                lower, upper, addTrend, autoScale):
407
-    """Uses rrdtool to create a graph of specified node data item.
408
-       Parameters:
409
-           fileName - name of file containing the graph
410
-           dataItem - data item to be graphed
411
-           gLabel - string containing a graph label for the data item
412
-           gTitle - string containing a title for the graph
413
-           gStart - beginning time of the graphed data
414
-           lower - lower bound for graph ordinate #NOT USED
415
-           upper - upper bound for graph ordinate #NOT USED
416
-           addTrend - 0, show only graph data
417
-                      1, show only a trend line
418
-                      2, show a trend line and the graph data
419
-           autoScale - if True, then use vertical axis auto scaling
420
-               (lower and upper parameters are ignored), otherwise use
421
-               lower and upper parameters to set vertical axis scale
422
-       Returns: True if successful, False otherwise
423
-    """
424
-    gPath = _CHARTS_DIRECTORY + fileName + ".png"
425
-    trendWindow = { 'end-1day': 7200,
426
-                    'end-4weeks': 172800,
427
-                    'end-12months': 604800 }
428
- 
429
-    # Format the rrdtool graph command.
430
-
431
-    # Set chart start time, height, and width.
432
-    strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \
433
-             % (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT)
434
-   
435
-    # Set the range and scaling of the chart y-axis.
436
-    if lower < upper:
437
-        strCmd  +=  "-l %s -u %s -r " % (lower, upper)
438
-    elif autoScale:
439
-        strCmd += "-A "
440
-    strCmd += "-Y "
441
-
442
-    # Set the chart ordinate label and chart title. 
443
-    strCmd += "-v %s -t %s " % (gLabel, gTitle)
444
- 
445
-    # Show the data, or a moving average trend line over
446
-    # the data, or both.
447
-    strCmd += "DEF:dSeries=%s:%s:LAST " % (_RRD_FILE, dataItem)
448
-    if addTrend == 0:
449
-        strCmd += "LINE1:dSeries#0400ff "
450
-    elif addTrend == 1:
451
-        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
452
-                  % trendWindow[gStart]
453
-    elif addTrend == 2:
454
-        strCmd += "LINE1:dSeries#0400ff "
455
-        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
456
-                  % trendWindow[gStart]
457
-     
458
-    if verboseDebug:
459
-        print "%s" % strCmd # DEBUG
460
-    
461
-    # Run the formatted rrdtool command as a subprocess.
462
-    try:
463
-        result = subprocess.check_output(strCmd, \
464
-                     stderr=subprocess.STDOUT,   \
465
-                     shell=True)
466
-    except subprocess.CalledProcessError, exError:
467
-        print "rrdtool graph failed: %s" % (exError.output)
468
-        return False
469
-
470
-    if debugOption:
471
-        print "rrdtool graph: %s\n" % result,
472
-    return True
473
-
474
-##end def
475
-
476
-def generateGraphs():
477
-    """Generate graphs for display in html documents.
478
-       Parameters: none
479
-       Returns: nothing
480
-    """
481
-    autoScale = False
482
-
483
-    # The following will force creation of charts
484
-    # of only signal strength and S/N charts.  Note that the following
485
-    # data items appear constant and do not show variation with time:
486
-    # noise level, rx mcs, rx rate, tx mcs, tx rate.  Therefore, until
487
-    # these parameters are demonstrated to vary in time, there is no point
488
-    # in creating the charts for these data items.
489
-    createAllCharts = False
490
-
491
-    # 24 hour stock charts
492
-
493
-    createGraph('24hr_signal', 'S', 'dBm', 
494
-                'RSSI\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
495
-    createGraph('24hr_snr', 'SNR', 'dB', 
496
-                'SNR\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
497
-
498
-    if createAllCharts:
499
-        createGraph('24hr_noise', 'N', 'dBm', 
500
-                    'Noise\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
501
-                    autoScale)
502
-        createGraph('24hr_rx_rate', 'RX_RATE', 'Mbps',
503
-                    'Rx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
504
-                    autoScale)
505
-        createGraph('24hr_tx_rate', 'TX_RATE', 'Mbps',
506
-                    'Tx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
507
-                    autoScale)
508
-        createGraph('24hr_rx_mcs', 'RX_MCS', 'Index',
509
-                    'Rx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
510
-                     autoScale)
511
-        createGraph('24hr_tx_mcs', 'TX_MCS', 'Index',
512
-                    'Tx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
513
-                     autoScale)
514
-
515
-    # 4 week stock charts
516
-
517
-    createGraph('4wk_signal', 'S', 'dBm', 
518
-                'RSSI\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
519
-    createGraph('4wk_snr', 'SNR', 'dB', 
520
-                'SNR\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
521
-
522
-    if createAllCharts:
523
-        createGraph('4wk_noise', 'N', 'dBm', 
524
-                    'Noise\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
525
-                    autoScale)
526
-        createGraph('4wk_rx_rate', 'RX_RATE', 'Mbps',
527
-                    'Rx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
528
-                    autoScale)
529
-        createGraph('4wk_tx_rate', 'TX_RATE', 'Mbps',
530
-                    'Tx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
531
-                    autoScale)
532
-        createGraph('4wk_rx_mcs', 'RX_MCS', 'Index',
533
-                    'Rx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
534
-                    autoScale)
535
-        createGraph('4wk_tx_mcs', 'TX_MCS', 'Index',
536
-                    'Tx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
537
-                    autoScale)
538
-
539
-    # 12 month stock charts
540
-
541
-    createGraph('12m_signal', 'S', 'dBm', 
542
-                'RSSI\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
543
-    createGraph('12m_snr', 'SNR', 'dB', 
544
-                'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
545
-
546
-    if createAllCharts:
547
-        createGraph('12m_noise', 'N', 'dBm', 
548
-                    'Noise\ -\ Past\ Year', 'end-12months', 0, 0, 2,
549
-                    autoScale)
550
-        createGraph('12m_rx_rate', 'RX_RATE', 'Mbps',
551
-                    'Rx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2,
552
-                    autoScale)
553
-        createGraph('12m_tx_rate', 'TX_RATE', 'Mbps',
554
-                    'Tx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2,
555
-                    autoScale)
556
-        createGraph('12m_rx_mcs', 'RX_MCS', 'Index',
557
-                    'Rx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2,
558
-                    autoScale)
559
-        createGraph('12m_tx_mcs', 'TX_MCS', 'Index',
560
-                    'Tx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2,
561
-                    autoScale)
562
-    if debugOption:
563
-        #print # print a blank line to improve readability when in debug mode
564
-        pass
565
-##end def
566
-
567
-def getCLarguments():
568
-    """Get command line arguments.  There are four possible arguments
569
-          -d turns on debug mode
570
-          -v turns on verbose debug mode
571
-          -t sets the aredn node query interval
572
-          -u sets the url of the aredn nodeing device
573
-       Returns: nothing
574
-    """
575
-    global debugOption, verboseDebug, dataRequestInterval, \
576
-           arednNodeUrl
577
-
578
-    index = 1
579
-    while index < len(sys.argv):
580
-        if sys.argv[index] == '-d':
581
-            debugOption = True
582
-        elif sys.argv[index] == '-v':
583
-            debugOption = True
584
-            verboseDebug = True
585
-        elif sys.argv[index] == '-p':
586
-            try:
587
-                dataRequestInterval = abs(int(sys.argv[index + 1]))
588
-            except:
589
-                print "invalid polling period"
590
-                exit(-1)
591
-            index += 1
592
-        elif sys.argv[index] == '-u':
593
-            arednNodeUrl = sys.argv[index + 1]
594
-            index += 1
595
-        else:
596
-            cmd_name = sys.argv[0].split('/')
597
-            print "Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1]
598
-            exit(-1)
599
-        index += 1
600
-##end def
601
-
602
-def main():
603
-    """Handles timing of events and acts as executive routine managing
604
-       all other functions.
605
-       Parameters: none
606
-       Returns: nothing
607
-    """
608
-    global dataRequestInterval
609
-
610
-    signal.signal(signal.SIGTERM, terminateAgentProcess)
611
-
612
-    print '%s starting up arednsig agent process' % \
613
-                  (getTimeStamp())
614
-
615
-    # last time output JSON file updated
616
-    lastDataRequestTime = -1
617
-    # last time charts generated
618
-    lastChartUpdateTime = - 1
619
-    # last time the rrdtool database updated
620
-    lastDatabaseUpdateTime = -1
621
-
622
-    ## Get command line arguments.
623
-    getCLarguments()
624
-
625
-    requestIntervalSeconds = dataRequestInterval * 60 # convert to seconds
626
-    chartUpdateInterval = dataRequestInterval # get charts interval
627
-
628
-    ## Exit with error if rrdtool database does not exist.
629
-    if not os.path.exists(_RRD_FILE):
630
-        print 'rrdtool database does not exist\n' \
631
-              'use createArednsigRrd script to ' \
632
-              'create rrdtool database\n'
633
-        exit(1)
634
- 
635
-    ## main loop
636
-    while True:
637
-
638
-        currentTime = time.time() # get current time in seconds
639
-
640
-        # Every web update interval request data from the aredn
641
-        # node and process the received data.
642
-        if currentTime - lastDataRequestTime > requestIntervalSeconds:
643
-            lastDataRequestTime = currentTime
644
-            ldData = []
645
-            result = True
646
-
647
-            # Get the data string from the device.
648
-            sData = getArednNodeData()
649
-            # If the first http request fails, try one more time.
650
-            if sData == None:
651
-                time.sleep(5)
652
-                sData = getArednNodeData()
653
-                if sData == None:
654
-                    result = False
655
-
656
-            # If successful parse the data.
657
-            if result:
658
-                result = parseNodeData(sData, ldData)
659
-           
660
-            # If parsing successful, convert the data.
661
-            if result:
662
-                result = convertData(ldData)
663
-
664
-            # If conversion successful, write data to data files.
665
-            if result:
666
-                result = updateDatabase(ldData)
667
-
668
-            if result:
669
-                writeOutputDataFile(sData, ldData)
670
-
671
-            # Set the node status to online or offline depending on the
672
-            # success or failure of the above operations.
673
-            setNodeStatus(result)
674
-
675
-
676
-        # At the chart generation interval, generate charts.
677
-        if currentTime - lastChartUpdateTime > chartUpdateInterval:
678
-            lastChartUpdateTime = currentTime
679
-            p = multiprocessing.Process(target=generateGraphs, args=())
680
-            p.start()
681
-
682
-        # Relinquish processing back to the operating system until
683
-        # the next update interval.
684
-
685
-        elapsedTime = time.time() - currentTime
686
-        if debugOption:
687
-            if result:
688
-                print "%s update successful:" % getTimeStamp(),
689
-            else:
690
-                print "%s update failed:" % getTimeStamp(),
691
-            print "%6f seconds processing time\n" % elapsedTime 
692
-        remainingTime = requestIntervalSeconds - elapsedTime
693
-        if remainingTime > 0.0:
694
-            time.sleep(remainingTime)
695
-    ## end while
696
-    return
697
-## end def
698
-
699
-if __name__ == '__main__':
700
-    try:
701
-        main()
702
-    except KeyboardInterrupt:
703
-        print '\n',
704
-        terminateAgentProcess('KeyboardInterrupt','Module')
Browse code

bug fix

gandolf authored on 02/13/2020 22:20:44
Showing 1 changed files
... ...
@@ -30,6 +30,9 @@
30 30
 #
31 31
 # Revision History
32 32
 #   * v20 released 11 Jan 2020 by J L Owrey; first release
33
+#   * v21 released 13 Feb 2020 by J L Owrey; fixed bug occuring when node
34
+#     powers on and signal data memory is empty.  Data points with N/A data
35
+#     are discarded.
33 36
 #
34 37
 #2345678901234567890123456789012345678901234567890123456789012345678901234567890
35 38
 
... ...
@@ -51,7 +54,7 @@ _HOSTNAME = os.uname()[1]
51 54
 
52 55
 _DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
53 56
 
54
-if _HOSTNAME == "{my alternate host}": 
57
+if _HOSTNAME == "raspi2": 
55 58
     _DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
56 59
 
57 60
     ### FILE AND FOLDER LOCATIONS ###
... ...
@@ -222,7 +225,8 @@ def parseNodeData(sData, ldData):
222 225
         ldTmp = json.loads(sData[1:-1])
223 226
         ldTmp = ldTmp[-iTrail:]
224 227
         if len(ldTmp) != iTrail:
225
-            raise Exception("truncated list")
228
+            #raise Exception("truncated list")
229
+            pass
226 230
     except Exception, exError:
227 231
         print "%s parse failed: %s" % (getTimeStamp(), exError)
228 232
         return False
... ...
@@ -247,7 +251,9 @@ def convertData(ldData):
247 251
     #  u'label': u'01/10/2020 22:17:01', u'rx_rate': u'130',
248 252
     #  u'y': [-48, -95], u'x': 1578694621000, u'tx_rate': u'130'}
249 253
     #
250
-    for item in ldData:
254
+    index = 0
255
+    while index < len(ldData):
256
+        item = ldData[index]
251 257
         try:
252 258
             item['time'] = int(item.pop('x')) / 1000
253 259
             item['signal'] = int(item['y'][0])
... ...
@@ -260,12 +266,21 @@ def convertData(ldData):
260 266
             item.pop('y')
261 267
             item.pop('label')
262 268
         except Exception, exError:
263
-            print "%s convert data failed: %s" % (getTimeStamp(), exError)
264
-            return False
269
+            print "%s convert data item failed: %s" % (getTimeStamp(), exError)
270
+            print "discarding %s" % item
271
+            #return False
272
+            ldData.pop(index)
273
+        else:
274
+            index += 1
265 275
     ##end for
266
-    if verboseDebug:
267
-        print "convert data successful"
268
-    return True
276
+
277
+    if len(ldData) > 0:
278
+        if verboseDebug:
279
+            print "convert data successful"
280
+        return True
281
+    else:
282
+        print "convert data failed"
283
+        return False
269 284
 ##end def
270 285
 
271 286
 def updateDatabase(ldData):
Browse code

minor update

gandolf authored on 01/23/2020 23:40:19
Showing 1 changed files
... ...
@@ -49,13 +49,10 @@ _HOSTNAME = os.uname()[1]
49 49
 
50 50
 # set url of the aredn node
51 51
 
52
-if _HOSTNAME == "ka7jlo-raspi1":
53
-    _DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
54
-elif _HOSTNAME == "raspi2": 
52
+_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
53
+
54
+if _HOSTNAME == "{my alternate host}": 
55 55
     _DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
56
-else:
57
-    print "unknown host"
58
-    exit(1)
59 56
 
60 57
     ### FILE AND FOLDER LOCATIONS ###
61 58
 
Browse code

update

gandolf authored on 01/23/2020 23:34:32
Showing 1 changed files
... ...
@@ -43,12 +43,19 @@ import time
43 43
 import json
44 44
 
45 45
 _USER = os.environ['USER']
46
+_HOSTNAME = os.uname()[1]
46 47
 
47 48
    ### DEFAULT AREDN NODE URL ###
48 49
 
49
-# ip address of the aredn node
50
+# set url of the aredn node
50 51
 
51
-_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
52
+if _HOSTNAME == "ka7jlo-raspi1":
53
+    _DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
54
+elif _HOSTNAME == "raspi2": 
55
+    _DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
56
+else:
57
+    print "unknown host"
58
+    exit(1)
52 59
 
53 60
     ### FILE AND FOLDER LOCATIONS ###
54 61
 
... ...
@@ -604,7 +611,6 @@ def main():
604 611
     getCLarguments()
605 612
 
606 613
     requestIntervalSeconds = dataRequestInterval * 60 # convert to seconds
607
-
608 614
     chartUpdateInterval = dataRequestInterval # get charts interval
609 615
 
610 616
     ## Exit with error if rrdtool database does not exist.
Browse code

bug fixes

gandolf authored on 01/23/2020 07:02:13
Showing 1 changed files
... ...
@@ -48,8 +48,7 @@ _USER = os.environ['USER']
48 48
 
49 49
 # ip address of the aredn node
50 50
 
51
-#_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
52
-_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
51
+_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
53 52
 
54 53
     ### FILE AND FOLDER LOCATIONS ###
55 54
 
Browse code

bug fixes

gandolf authored on 01/23/2020 07:00:12
Showing 1 changed files
... ...
@@ -47,7 +47,9 @@ _USER = os.environ['USER']
47 47
    ### DEFAULT AREDN NODE URL ###
48 48
 
49 49
 # ip address of the aredn node
50
-_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
50
+
51
+#_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
52
+_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
51 53
 
52 54
     ### FILE AND FOLDER LOCATIONS ###
53 55
 
Browse code

corrections

gandolf authored on 01/22/2020 06:10:06
Showing 1 changed files
... ...
@@ -47,8 +47,7 @@ _USER = os.environ['USER']
47 47
    ### DEFAULT AREDN NODE URL ###
48 48
 
49 49
 # ip address of the aredn node
50
-#_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
51
-_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
50
+_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
52 51
 
53 52
     ### FILE AND FOLDER LOCATIONS ###
54 53
 
Browse code

bug fixes

gandolf authored on 01/22/2020 05:51:25
Showing 1 changed files
... ...
@@ -11,7 +11,7 @@
11 11
 #     - conversion of data items
12 12
 #     - update a round robin (rrdtool) database with the node data
13 13
 #     - periodically generate graphic charts for display in html documents
14
-#     - write the processed radmon data to a JSON file for use by html
14
+#     - write the processed node status to a JSON file for use by html
15 15
 #       documents
16 16
 #
17 17
 # Copyright 2020 Jeff Owrey
... ...
@@ -47,7 +47,8 @@ _USER = os.environ['USER']
47 47
    ### DEFAULT AREDN NODE URL ###
48 48
 
49 49
 # ip address of the aredn node
50
-_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30/cgi-bin/signal.json"
50
+#_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json"
51
+_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json"
51 52
 
52 53
     ### FILE AND FOLDER LOCATIONS ###
53 54
 
... ...
@@ -59,7 +60,7 @@ _CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
59 60
 _OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
60 61
 # dummy output data file
61 62
 _DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
62
-# database that stores radmon data
63
+# database that stores node data
63 64
 _RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
64 65
 
65 66
     ### GLOBAL CONSTANTS ###
... ...
@@ -74,8 +75,9 @@ _HTTP_REQUEST_TIMEOUT = 10
74 75
 _CHART_WIDTH = 600
75 76
 # standard chart height in pixels
76 77
 _CHART_HEIGHT = 150
77
-# source of time stamp attached to output data file
78
-_USE_NODE_TIMESTAMP = True
78
+# Set this to True only if this server is intended to relay raw
79
+# node data to a mirror server.
80
+_RELAY_SERVER = False
79 81
 
80 82
    ### GLOBAL VARIABLES ###
81 83
 
... ...
@@ -91,8 +93,6 @@ failedUpdateCount = 0
91 93
 # detected status of aredn node device
92 94
 nodeOnline = True
93 95
 
94
-# status of reset command to aredn node
95
-remoteDeviceReset = False
96 96
 # ip address of aredn node
97 97
 arednNodeUrl = _DEFAULT_AREDN_NODE_URL
98 98
 # frequency of data requests to aredn node
... ...
@@ -322,9 +322,6 @@ def writeOutputDataFile(sData, ldData):
322 322
                    to the output data file
323 323
        Returns: True if successful, False otherwise
324 324
     """
325
-    if verboseDebug:
326
-        print "write output data file: %d bytes" % len(sData)
327
-
328 325
     # Write file for use by html clients.  The following two
329 326
     # data items are sent to the client file.
330 327
     #    * The last database update date and time
... ...
@@ -338,18 +335,22 @@ def writeOutputDataFile(sData, ldData):
338 335
         fc.write(sDate)
339 336
         fc.close()
340 337
     except Exception, exError:
341
-        print "%s write output file failed: %s" % (getTimeStamp(), exError)
338
+        print "%s write node file failed: %s" % (getTimeStamp(), exError)
342 339
         return False
343
-    return True
344 340
 
345
-    # Write the entire node data response to the output data file.
346
-    try:
347
-        fc = open(_OUTPUT_DATA_FILE, "w")
348
-        fc.write(sData)
349
-        fc.close()
350
-    except Exception, exError:
351
-        print "%s write output file failed: %s" % (getTimeStamp(), exError)
352
-        return False
341
+    if _RELAY_SERVER:
342
+        # Write the entire node data response to the output data file.
343
+        try:
344
+            fc = open(_OUTPUT_DATA_FILE, "w")
345
+            fc.write(sData)
346
+            fc.close()
347
+        except Exception, exError:
348
+            print "%s write output file failed: %s" % \
349
+                  (getTimeStamp(), exError)
350
+            return False
351
+        if verboseDebug:
352
+            print "write output data file: %d bytes" % len(sData)
353
+
353 354
     return True
354 355
 ## end def
355 356
 
... ...
@@ -384,7 +385,7 @@ def setNodeStatus(updateSuccess):
384 385
 
385 386
 def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
386 387
                 lower, upper, addTrend, autoScale):
387
-    """Uses rrdtool to create a graph of specified radmon data item.
388
+    """Uses rrdtool to create a graph of specified node data item.
388 389
        Parameters:
389 390
            fileName - name of file containing the graph
390 391
            dataItem - data item to be graphed
... ...
@@ -436,7 +437,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
436 437
                   % trendWindow[gStart]
437 438
      
438 439
     if verboseDebug:
439
-        print "\n%s" % strCmd # DEBUG
440
+        print "%s" % strCmd # DEBUG
440 441
     
441 442
     # Run the formatted rrdtool command as a subprocess.
442 443
     try:
... ...
@@ -448,7 +449,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
448 449
         return False
449 450
 
450 451
     if debugOption:
451
-        print "rrdtool graph: %s" % result,
452
+        print "rrdtool graph: %s\n" % result,
452 453
     return True
453 454
 
454 455
 ##end def
... ...
@@ -460,50 +461,88 @@ def generateGraphs():
460 461
     """
461 462
     autoScale = False
462 463
 
464
+    # The following will force creation of charts
465
+    # of only signal strength and S/N charts.  Note that the following
466
+    # data items appear constant and do not show variation with time:
467
+    # noise level, rx mcs, rx rate, tx mcs, tx rate.  Therefore, until
468
+    # these parameters are demonstrated to vary in time, there is no point
469
+    # in creating the charts for these data items.
470
+    createAllCharts = False
471
+
472
+    # 24 hour stock charts
473
+
463 474
     createGraph('24hr_signal', 'S', 'dBm', 
464 475
                 'RSSI\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
465
-    createGraph('24hr_noise', 'N', 'dBm', 
466
-                'Noise\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
467 476
     createGraph('24hr_snr', 'SNR', 'dB', 
468 477
                 'SNR\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
469
-    createGraph('24hr_rx_rate', 'RX_RATE', 'Mbps',
470
-                'Rx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
471
-    createGraph('24hr_tx_rate', 'TX_RATE', 'Mbps',
472
-                'Tx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
473
-    #createGraph('24hr_rx_mcs', 'RX_MCS', 'Index',
474
-    #            'Rx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
475
-    #createGraph('24hr_tx_mcs', 'TX_MCS', 'Index',
476
-    #            'Tx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
478
+
479
+    if createAllCharts:
480
+        createGraph('24hr_noise', 'N', 'dBm', 
481
+                    'Noise\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
482
+                    autoScale)
483
+        createGraph('24hr_rx_rate', 'RX_RATE', 'Mbps',
484
+                    'Rx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
485
+                    autoScale)
486
+        createGraph('24hr_tx_rate', 'TX_RATE', 'Mbps',
487
+                    'Tx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
488
+                    autoScale)
489
+        createGraph('24hr_rx_mcs', 'RX_MCS', 'Index',
490
+                    'Rx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
491
+                     autoScale)
492
+        createGraph('24hr_tx_mcs', 'TX_MCS', 'Index',
493
+                    'Tx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2,
494
+                     autoScale)
495
+
496
+    # 4 week stock charts
477 497
 
478 498
     createGraph('4wk_signal', 'S', 'dBm', 
479 499
                 'RSSI\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
480
-    createGraph('4wk_noise', 'N', 'dBm', 
481
-                'Noise\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
482 500
     createGraph('4wk_snr', 'SNR', 'dB', 
483 501
                 'SNR\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
484
-    createGraph('4wk_rx_rate', 'RX_RATE', 'Mbps',
485
-                'Rx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
486
-    createGraph('4wk_tx_rate', 'TX_RATE', 'Mbps',
487
-                'Tx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
488
-    #createGraph('4wk_rx_mcs', 'RX_MCS', 'Index',
489
-    #            'Rx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
490
-    #createGraph('4wk_tx_mcs', 'TX_MCS', 'Index',
491
-    #            'Tx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
502
+
503
+    if createAllCharts:
504
+        createGraph('4wk_noise', 'N', 'dBm', 
505
+                    'Noise\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
506
+                    autoScale)
507
+        createGraph('4wk_rx_rate', 'RX_RATE', 'Mbps',
508
+                    'Rx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
509
+                    autoScale)
510
+        createGraph('4wk_tx_rate', 'TX_RATE', 'Mbps',
511
+                    'Tx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
512
+                    autoScale)
513
+        createGraph('4wk_rx_mcs', 'RX_MCS', 'Index',
514
+                    'Rx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
515
+                    autoScale)
516
+        createGraph('4wk_tx_mcs', 'TX_MCS', 'Index',
517
+                    'Tx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2,
518
+                    autoScale)
519
+
520
+    # 12 month stock charts
492 521
 
493 522
     createGraph('12m_signal', 'S', 'dBm', 
494 523
                 'RSSI\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
495
-    createGraph('12m_noise', 'N', 'dBm', 
496
-                'Noise\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
497 524
     createGraph('12m_snr', 'SNR', 'dB', 
498 525
                 'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
499
-    createGraph('12m_rx_rate', 'RX_RATE', 'Mbps',
500
-                'Rx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
501
-    createGraph('12m_tx_rate', 'TX_RATE', 'Mbps',
502
-                'Tx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
503
-    #createGraph('12m_rx_mcs', 'RX_MCS', 'Index',
504
-    #            'Rx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
505
-    #createGraph('12m_tx_mcs', 'TX_MCS', 'Index',
506
-    #            'Tx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
526
+
527
+    if createAllCharts:
528
+        createGraph('12m_noise', 'N', 'dBm', 
529
+                    'Noise\ -\ Past\ Year', 'end-12months', 0, 0, 2,
530
+                    autoScale)
531
+        createGraph('12m_rx_rate', 'RX_RATE', 'Mbps',
532
+                    'Rx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2,
533
+                    autoScale)
534
+        createGraph('12m_tx_rate', 'TX_RATE', 'Mbps',
535
+                    'Tx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2,
536
+                    autoScale)
537
+        createGraph('12m_rx_mcs', 'RX_MCS', 'Index',
538
+                    'Rx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2,
539
+                    autoScale)
540
+        createGraph('12m_tx_mcs', 'TX_MCS', 'Index',
541
+                    'Tx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2,
542
+                    autoScale)
543
+    if debugOption:
544
+        #print # print a blank line to improve readability when in debug mode
545
+        pass
507 546
 ##end def
508 547
 
509 548
 def getCLarguments():
... ...
@@ -536,7 +575,7 @@ def getCLarguments():
536 575
             index += 1
537 576
         else:
538 577
             cmd_name = sys.argv[0].split('/')
539
-            print "Usage: %s [-d] [-v] [-pt seconds] [-u url}" % cmd_name[-1]
578
+            print "Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1]
540 579
             exit(-1)
541 580
         index += 1
542 581
 ##end def
... ...
@@ -566,12 +605,12 @@ def main():
566 605
 
567 606
     requestIntervalSeconds = dataRequestInterval * 60 # convert to seconds
568 607
 
569
-    chartUpdateInterval = dataRequestInterval # get charts when updating database
608
+    chartUpdateInterval = dataRequestInterval # get charts interval
570 609
 
571 610
     ## Exit with error if rrdtool database does not exist.
572 611
     if not os.path.exists(_RRD_FILE):
573 612
         print 'rrdtool database does not exist\n' \
574
-              'use createRadmonRrd script to ' \
613
+              'use createArednsigRrd script to ' \
575 614
               'create rrdtool database\n'
576 615
         exit(1)
577 616
  
... ...
@@ -630,7 +669,7 @@ def main():
630 669
             if result:
631 670
                 print "%s update successful:" % getTimeStamp(),
632 671
             else:
633
-               print "%s update failed:" % getTimeStamp(),
672
+                print "%s update failed:" % getTimeStamp(),
634 673
             print "%6f seconds processing time\n" % elapsedTime 
635 674
         remainingTime = requestIntervalSeconds - elapsedTime
636 675
         if remainingTime > 0.0:
Browse code

custom charts added

gandolf authored on 01/17/2020 00:20:38
Showing 1 changed files
... ...
@@ -5,7 +5,7 @@
5 5
 # Module: arednsigAgent.py
6 6
 #
7 7
 # Description: This module acts as an agent between the aredn node
8
-# and aredn mesh web services.  The agent periodically sends an http
8
+# and aredn mest services.  The agent periodically sends an http
9 9
 # request to the aredn node, processes the response from
10 10
 # the node, and performs a number of operations:
11 11
 #     - conversion of data items
... ...
@@ -57,8 +57,8 @@ _DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
57 57
 _CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
58 58
 # location of data output file
59 59
 _OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
60
-# small size output data file for heartbeat signal to html docs
61
-_SMALL_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
60
+# dummy output data file
61
+_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
62 62
 # database that stores radmon data
63 63
 _RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
64 64
 
... ...
@@ -74,6 +74,8 @@ _HTTP_REQUEST_TIMEOUT = 10
74 74
 _CHART_WIDTH = 600
75 75
 # standard chart height in pixels
76 76
 _CHART_HEIGHT = 150
77
+# source of time stamp attached to output data file
78
+_USE_NODE_TIMESTAMP = True
77 79
 
78 80
    ### GLOBAL VARIABLES ###
79 81
 
... ...
@@ -89,7 +91,9 @@ failedUpdateCount = 0
89 91
 # detected status of aredn node device
90 92
 nodeOnline = True
91 93
 
92
-# network address of aredn node
94
+# status of reset command to aredn node
95
+remoteDeviceReset = False
96
+# ip address of aredn node
93 97
 arednNodeUrl = _DEFAULT_AREDN_NODE_URL
94 98
 # frequency of data requests to aredn node
95 99
 dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
... ...
@@ -142,8 +146,8 @@ def setStatusToOffline():
142 146
     # Inform downstream clients by removing output data file.
143 147
     if os.path.exists(_OUTPUT_DATA_FILE):
144 148
        os.remove(_OUTPUT_DATA_FILE)
145
-    if os.path.exists(_SMALL_OUTPUT_FILE):
146
-       os.remove(_SMALL_OUTPUT_FILE)
149
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
150
+       os.remove(_DUMMY_OUTPUT_FILE)
147 151
     # If the aredn node was previously online, then send
148 152
     # a message that we are now offline.
149 153
     if nodeOnline:
... ...
@@ -162,8 +166,8 @@ def terminateAgentProcess(signal, frame):
162 166
     # Inform downstream clients by removing output data file.
163 167
     if os.path.exists(_OUTPUT_DATA_FILE):
164 168
        os.remove(_OUTPUT_DATA_FILE)
165
-    if os.path.exists(_SMALL_OUTPUT_FILE):
166
-       os.remove(_SMALL_OUTPUT_FILE)
169
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
170
+       os.remove(_DUMMY_OUTPUT_FILE)
167 171
     print '%s terminating arednsig agent process' % \
168 172
               (getTimeStamp())
169 173
     sys.exit(0)
... ...
@@ -188,8 +192,8 @@ def getArednNodeData():
188 192
         del conn
189 193
 
190 194
     except Exception, exError:
191
-        # If no response is received from the node, then assume that
192
-        # the node is down or unavailable over the network.  In
195
+        # If no response is received from the device, then assume that
196
+        # the device is down or unavailable over the network.  In
193 197
         # that case return None to the calling function.
194 198
         print "%s http error: %s" % (getTimeStamp(), exError)
195 199
         return None
... ...
@@ -205,23 +209,11 @@ def parseNodeData(sData, ldData):
205 209
        into its component parts.  
206 210
        Parameters:
207 211
            sData - the string containing the data to be parsed
208
-           ldData - a list object to contain the parsed data items
212
+           dData - a dictionary object to contain the parsed data items
209 213
        Returns: True if successful, False otherwise
210 214
     """
211
-    # Only interested in datapoints that have arrived during the
212
-    # current update cycle defined by the data request interval.
213
-    # The default is sixty minutes, and the node records a data point
214
-    # once a minute.  Therefore, in the default case, extract the last
215
-    # sixty data points from the node's response.  Otherwise, extract
216
-    # a number of datapoints equal the the data request interval
217
-    # (in minutes). 
218 215
     iTrail = int(dataRequestInterval)
219 216
 
220
-    # The node response with json object containing 24 hours worth
221
-    # of datapoints.  Each data point is, itself, a json object.
222
-    # The following code converts the json object to a python list
223
-    # object containing dictionary objects.  Each dictionary object
224
-    # stores one data point.
225 217
     try:
226 218
         ldTmp = json.loads(sData[1:-1])
227 219
         ldTmp = ldTmp[-iTrail:]
... ...
@@ -230,9 +222,7 @@ def parseNodeData(sData, ldData):
230 222
     except Exception, exError:
231 223
         print "%s parse failed: %s" % (getTimeStamp(), exError)
232 224
         return False
233
-       
234
-    # Store the dictionary objects in the list object passed by
235
-    # reference to this function.
225
+    
236 226
     del ldData[:]
237 227
     for item in ldTmp:
238 228
         ldData.append(item)
... ...
@@ -245,7 +235,7 @@ def parseNodeData(sData, ldData):
245 235
 def convertData(ldData):
246 236
     """Convert individual node signal data items as necessary.
247 237
        Parameters:
248
-           ldData - a list object containing the node signal data
238
+           dData - a dictionary object containing the node signal data
249 239
        Returns: True if successful, False otherwise
250 240
     """
251 241
     # parse example string
... ...
@@ -255,9 +245,6 @@ def convertData(ldData):
255 245
     #
256 246
     for item in ldData:
257 247
         try:
258
-            # Convert data items to the required data types
259
-            # (all integer in this case).  Change the dictionary
260
-            # object keys to more friendly names.
261 248
             item['time'] = int(item.pop('x')) / 1000
262 249
             item['signal'] = int(item['y'][0])
263 250
             item['noise'] = int(item['y'][1])
... ...
@@ -282,8 +269,8 @@ def updateDatabase(ldData):
282 269
     Update the rrdtool database by executing an rrdtool system command.
283 270
     Format the command using the data extracted from the aredn node
284 271
     response.   
285
-    Parameters: ldData - a list object containing data items to be
286
-                        written to the RRD file
272
+    Parameters: dData - dictionary object containing data items to be
273
+                        written to the rr database file
287 274
     Returns: True if successful, False otherwise
288 275
     """
289 276
     updateCount = 0
... ...
@@ -293,9 +280,7 @@ def updateDatabase(ldData):
293 280
          print "updating database..."
294 281
 
295 282
     for item in ldData:
296
-        # Throw out data points that have a time stamp earlier
297
-        # than the last entry in the round-robin database (RRD).
298
-        # rrdtool will throw an error in this case.
283
+
299 284
         if item['time'] <= lastDataPointTime:
300 285
             if verboseDebug:
301 286
                 print "%s invalid timestamp: discarding data" % \
... ...
@@ -349,7 +334,7 @@ def writeOutputDataFile(sData, ldData):
349 334
     sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
350 335
            (lastUpdate, dataRequestInterval)
351 336
     try:
352
-        fc = open(_SMALL_OUTPUT_FILE, "w")
337
+        fc = open(_DUMMY_OUTPUT_FILE, "w")
353 338
         fc.write(sDate)
354 339
         fc.close()
355 340
     except Exception, exError:
Browse code

initial release

gandolf authored on 01/13/2020 03:50:38
Showing 1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,662 @@
1
+#!/usr/bin/python -u
2
+# The -u option above turns off block buffering of python output. This 
3
+# assures that each error message gets individually printed to the log file.
4
+#
5
+# Module: arednsigAgent.py
6
+#
7
+# Description: This module acts as an agent between the aredn node
8
+# and aredn mesh web services.  The agent periodically sends an http
9
+# request to the aredn node, processes the response from
10
+# the node, and performs a number of operations:
11
+#     - conversion of data items
12
+#     - update a round robin (rrdtool) database with the node data
13
+#     - periodically generate graphic charts for display in html documents
14
+#     - write the processed radmon data to a JSON file for use by html
15
+#       documents
16
+#
17
+# Copyright 2020 Jeff Owrey
18
+#    This program is free software: you can redistribute it and/or modify
19
+#    it under the terms of the GNU General Public License as published by
20
+#    the Free Software Foundation, either version 3 of the License, or
21
+#    (at your option) any later version.
22
+#
23
+#    This program is distributed in the hope that it will be useful,
24
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
+#    GNU General Public License for more details.
27
+#
28
+#    You should have received a copy of the GNU General Public License
29
+#    along with this program.  If not, see http://www.gnu.org/license.
30
+#
31
+# Revision History
32
+#   * v20 released 11 Jan 2020 by J L Owrey; first release
33
+#
34
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
35
+
36
+import os
37
+import urllib2
38
+import sys
39
+import signal
40
+import subprocess
41
+import multiprocessing
42
+import time
43
+import json
44
+
45
+_USER = os.environ['USER']
46
+
47
+   ### DEFAULT AREDN NODE URL ###
48
+
49
+# ip address of the aredn node
50
+_DEFAULT_AREDN_NODE_URL = "http://192.168.1.30/cgi-bin/signal.json"
51
+
52
+    ### FILE AND FOLDER LOCATIONS ###
53
+
54
+# folder for containing dynamic data objects
55
+_DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
56
+# folder for charts and output data file
57
+_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
58
+# location of data output file
59
+_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
60
+# small size output data file for heartbeat signal to html docs
61
+_SMALL_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
62
+# database that stores radmon data
63
+_RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
64
+
65
+    ### GLOBAL CONSTANTS ###
66
+
67
+# max number of failed data requests allowed
68
+_MAX_FAILED_DATA_REQUESTS = 0
69
+# interval in minutes between data requests to the aredn node
70
+_DEFAULT_DATA_REQUEST_INTERVAL = 60
71
+# number seconds to wait for a response to HTTP request
72
+_HTTP_REQUEST_TIMEOUT = 10
73
+# standard chart width in pixels
74
+_CHART_WIDTH = 600
75
+# standard chart height in pixels
76
+_CHART_HEIGHT = 150
77
+
78
+   ### GLOBAL VARIABLES ###
79
+
80
+# turn on or off of verbose debugging information
81
+debugOption = False
82
+verboseDebug = False
83
+
84
+# The following two items are used for detecting system faults
85
+# and aredn node online or offline status.
86
+
87
+# count of failed attempts to get data from aredn node
88
+failedUpdateCount = 0
89
+# detected status of aredn node device
90
+nodeOnline = True
91
+
92
+# network address of aredn node
93
+arednNodeUrl = _DEFAULT_AREDN_NODE_URL
94
+# frequency of data requests to aredn node
95
+dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
96
+# last node request time
97
+lastDataPointTime = -1
98
+
99
+  ###  PRIVATE METHODS  ###
100
+
101
+def getTimeStamp():
102
+    """
103
+    Set the error message time stamp to the local system time.
104
+    Parameters: none
105
+    Returns: string containing the time stamp
106
+    """
107
+    return time.strftime( "%m/%d/%Y %T", time.localtime() )
108
+##end def
109
+
110
+def getLastDataPointTime():
111
+    """
112
+    Get the timestamp of the most recent update to the round robin
113
+    database.
114
+    Parameters: none
115
+    Returns: string epoch time stamp of the last rrd update
116
+    """
117
+    strCmd = "rrdtool lastupdate %s" % \
118
+             (_RRD_FILE)
119
+
120
+    # Run the command as a subprocess.
121
+    try:
122
+        result = subprocess.check_output(strCmd, shell=True,  \
123
+                             stderr=subprocess.STDOUT)
124
+    except subprocess.CalledProcessError, exError:
125
+        print "%s: rrdtool update failed: %s" % \
126
+                    (getTimeStamp(), exError.output)
127
+        return None
128
+
129
+    # Return just the epoch time stamp of the last rrd update
130
+    return int((result.split('\n')[-2]).split(':')[0])
131
+##end def
132
+
133
+def setStatusToOffline():
134
+    """Set the detected status of the aredn node to
135
+       "offline" and inform downstream clients by removing input
136
+       and output data files.
137
+       Parameters: none
138
+       Returns: nothing
139
+    """
140
+    global nodeOnline
141
+
142
+    # Inform downstream clients by removing output data file.
143
+    if os.path.exists(_OUTPUT_DATA_FILE):
144
+       os.remove(_OUTPUT_DATA_FILE)
145
+    if os.path.exists(_SMALL_OUTPUT_FILE):
146
+       os.remove(_SMALL_OUTPUT_FILE)
147
+    # If the aredn node was previously online, then send
148
+    # a message that we are now offline.
149
+    if nodeOnline:
150
+        print '%s aredn node offline' % getTimeStamp()
151
+    nodeOnline = False
152
+##end def
153
+
154
+def terminateAgentProcess(signal, frame):
155
+    """Send a message to log when the agent process gets killed
156
+       by the operating system.  Inform downstream clients
157
+       by removing input and output data files.
158
+       Parameters:
159
+           signal, frame - dummy parameters
160
+       Returns: nothing
161
+    """
162
+    # Inform downstream clients by removing output data file.
163
+    if os.path.exists(_OUTPUT_DATA_FILE):
164
+       os.remove(_OUTPUT_DATA_FILE)
165
+    if os.path.exists(_SMALL_OUTPUT_FILE):
166
+       os.remove(_SMALL_OUTPUT_FILE)
167
+    print '%s terminating arednsig agent process' % \
168
+              (getTimeStamp())
169
+    sys.exit(0)
170
+##end def
171
+
172
+  ###  PUBLIC METHODS  ###
173
+
174
+def getArednNodeData():
175
+    """Send http request to aredn node.  The response from the
176
+       node contains the node signal data as unformatted ascii text.
177
+       Parameters: none
178
+       Returns: a string containing the node signal data if successful,
179
+                or None if not successful
180
+    """
181
+    try:
182
+        conn = urllib2.urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT)
183
+
184
+        # Format received data into a single string.
185
+        content = ""
186
+        for line in conn:
187
+            content += line.strip()
188
+        del conn
189
+
190
+    except Exception, exError:
191
+        # If no response is received from the node, then assume that
192
+        # the node is down or unavailable over the network.  In
193
+        # that case return None to the calling function.
194
+        print "%s http error: %s" % (getTimeStamp(), exError)
195
+        return None
196
+
197
+    if verboseDebug:
198
+        print "http request successful: %d bytes" % len(content)
199
+
200
+    return content
201
+##end def
202
+
203
+def parseNodeData(sData, ldData):
204
+    """Parse the node  signal data JSON string from the aredn node
205
+       into its component parts.  
206
+       Parameters:
207
+           sData - the string containing the data to be parsed
208
+           ldData - a list object to contain the parsed data items
209
+       Returns: True if successful, False otherwise
210
+    """
211
+    # Only interested in datapoints that have arrived during the
212
+    # current update cycle defined by the data request interval.
213
+    # The default is sixty minutes, and the node records a data point
214
+    # once a minute.  Therefore, in the default case, extract the last
215
+    # sixty data points from the node's response.  Otherwise, extract
216
+    # a number of datapoints equal the the data request interval
217
+    # (in minutes). 
218
+    iTrail = int(dataRequestInterval)
219
+
220
+    # The node response with json object containing 24 hours worth
221
+    # of datapoints.  Each data point is, itself, a json object.
222
+    # The following code converts the json object to a python list
223
+    # object containing dictionary objects.  Each dictionary object
224
+    # stores one data point.
225
+    try:
226
+        ldTmp = json.loads(sData[1:-1])
227
+        ldTmp = ldTmp[-iTrail:]
228
+        if len(ldTmp) != iTrail:
229
+            raise Exception("truncated list")
230
+    except Exception, exError:
231
+        print "%s parse failed: %s" % (getTimeStamp(), exError)
232
+        return False
233
+       
234
+    # Store the dictionary objects in the list object passed by
235
+    # reference to this function.
236
+    del ldData[:]
237
+    for item in ldTmp:
238
+        ldData.append(item)
239
+
240
+    if verboseDebug:
241
+        print "parse successful: %d data points" % len(ldData)
242
+    return True
243
+##end def
244
+
245
+def convertData(ldData):
246
+    """Convert individual node signal data items as necessary.
247
+       Parameters:
248
+           ldData - a list object containing the node signal data
249
+       Returns: True if successful, False otherwise
250
+    """
251
+    # parse example string
252
+    # {u'tx_mcs': u'15', u'rx_mcs': u'15', u'm': 47,
253
+    #  u'label': u'01/10/2020 22:17:01', u'rx_rate': u'130',
254
+    #  u'y': [-48, -95], u'x': 1578694621000, u'tx_rate': u'130'}
255
+    #
256
+    for item in ldData:
257
+        try:
258
+            # Convert data items to the required data types
259
+            # (all integer in this case).  Change the dictionary
260
+            # object keys to more friendly names.
261
+            item['time'] = int(item.pop('x')) / 1000
262
+            item['signal'] = int(item['y'][0])
263
+            item['noise'] = int(item['y'][1])
264
+            item['snr'] = int(item.pop('m'))
265
+            item['rx_mcs'] = int(item.pop('rx_mcs'))
266
+            item['tx_mcs'] = int(item.pop('tx_mcs'))
267
+            item['rx_rate'] = int(item.pop('rx_rate'))
268
+            item['tx_rate'] = int(item.pop('tx_rate'))
269
+            item.pop('y')
270
+            item.pop('label')
271
+        except Exception, exError:
272
+            print "%s convert data failed: %s" % (getTimeStamp(), exError)
273
+            return False
274
+    ##end for
275
+    if verboseDebug:
276
+        print "convert data successful"
277
+    return True
278
+##end def
279
+
280
+def updateDatabase(ldData):
281
+    """
282
+    Update the rrdtool database by executing an rrdtool system command.
283
+    Format the command using the data extracted from the aredn node
284
+    response.   
285
+    Parameters: ldData - a list object containing data items to be
286
+                        written to the RRD file
287
+    Returns: True if successful, False otherwise
288
+    """
289
+    updateCount = 0
290
+    lastDataPointTime = getLastDataPointTime()
291
+
292
+    if verboseDebug:
293
+         print "updating database..."
294
+
295
+    for item in ldData:
296
+        # Throw out data points that have a time stamp earlier
297
+        # than the last entry in the round-robin database (RRD).
298
+        # rrdtool will throw an error in this case.
299
+        if item['time'] <= lastDataPointTime:
300
+            if verboseDebug:
301
+                print "%s invalid timestamp: discarding data" % \
302
+                      (getTimeStamp())
303
+            continue
304
+
305
+        # Format the rrdtool update command.
306
+        strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s"
307
+        strCmd = strFmt % (_RRD_FILE, item['time'], item['signal'], \
308
+                 item['noise'], item['snr'], item['rx_mcs'], \
309
+                 item['tx_mcs'], item['rx_rate'], item['tx_rate'])
310
+
311
+        if verboseDebug:
312
+            print "%s" % strCmd # DEBUG
313
+
314
+        # Run the command as a subprocess.
315
+        try:
316
+            subprocess.check_output(strCmd, shell=True,  \
317
+                                 stderr=subprocess.STDOUT)
318
+        except subprocess.CalledProcessError, exError:
319
+            print "%s: rrdtool update failed: %s" % \
320
+                        (getTimeStamp(), exError.output)
321
+            return False
322
+        updateCount += 1
323
+    ##end for
324
+
325
+    if debugOption:
326
+        print '%s added %d data points to database' % \
327
+              (getTimeStamp(), updateCount)
328
+    return True
329
+##end def
330
+
331
+def writeOutputDataFile(sData, ldData):
332
+    """Write node data items to the output data file, formatted as 
333
+       a Javascript file.  This file may then be accessed and used by
334
+       by downstream clients, for instance, in HTML documents.
335
+       Parameters:
336
+           sData - a string object containing the data to be written
337
+                   to the output data file
338
+       Returns: True if successful, False otherwise
339
+    """
340
+    if verboseDebug:
341
+        print "write output data file: %d bytes" % len(sData)
342
+
343
+    # Write file for use by html clients.  The following two
344
+    # data items are sent to the client file.
345
+    #    * The last database update date and time
346
+    #    * The data request interval
347
+    lastUpdate = time.strftime( "%m.%d.%Y %T", 
348
+                                time.localtime(ldData[-1]['time']) )
349
+    sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
350
+           (lastUpdate, dataRequestInterval)
351
+    try:
352
+        fc = open(_SMALL_OUTPUT_FILE, "w")
353
+        fc.write(sDate)
354
+        fc.close()
355
+    except Exception, exError:
356
+        print "%s write output file failed: %s" % (getTimeStamp(), exError)
357
+        return False
358
+    return True
359
+
360
+    # Write the entire node data response to the output data file.
361
+    try:
362
+        fc = open(_OUTPUT_DATA_FILE, "w")
363
+        fc.write(sData)
364
+        fc.close()
365
+    except Exception, exError:
366
+        print "%s write output file failed: %s" % (getTimeStamp(), exError)
367
+        return False
368
+    return True
369
+## end def
370
+
371
+def setNodeStatus(updateSuccess):
372
+    """Detect if aredn node is offline or not available on
373
+       the network. After a set number of attempts to get data
374
+       from the node set a flag that the node is offline.
375
+       Parameters:
376
+           updateSuccess - a boolean that is True if data request
377
+                           successful, False otherwise
378
+       Returns: nothing
379
+    """
380
+    global failedUpdateCount, nodeOnline
381
+
382
+    if updateSuccess:
383
+        failedUpdateCount = 0
384
+        # Set status and send a message to the log if the node was
385
+        # previously offline and is now online.
386
+        if not nodeOnline:
387
+            print '%s aredn node online' % getTimeStamp()
388
+            nodeOnline = True
389
+    else:
390
+        # The last attempt failed, so update the failed attempts
391
+        # count.
392
+        failedUpdateCount += 1
393
+
394
+    if failedUpdateCount > _MAX_FAILED_DATA_REQUESTS:
395
+        # Max number of failed data requests, so set
396
+        # node status to offline.
397
+        setStatusToOffline()
398
+##end def
399
+
400
+def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
401
+                lower, upper, addTrend, autoScale):
402
+    """Uses rrdtool to create a graph of specified radmon data item.
403
+       Parameters:
404
+           fileName - name of file containing the graph
405
+           dataItem - data item to be graphed
406
+           gLabel - string containing a graph label for the data item
407
+           gTitle - string containing a title for the graph
408
+           gStart - beginning time of the graphed data
409
+           lower - lower bound for graph ordinate #NOT USED
410
+           upper - upper bound for graph ordinate #NOT USED
411
+           addTrend - 0, show only graph data
412
+                      1, show only a trend line
413
+                      2, show a trend line and the graph data
414
+           autoScale - if True, then use vertical axis auto scaling
415
+               (lower and upper parameters are ignored), otherwise use
416
+               lower and upper parameters to set vertical axis scale
417
+       Returns: True if successful, False otherwise
418
+    """
419
+    gPath = _CHARTS_DIRECTORY + fileName + ".png"
420
+    trendWindow = { 'end-1day': 7200,
421
+                    'end-4weeks': 172800,
422
+                    'end-12months': 604800 }
423
+ 
424
+    # Format the rrdtool graph command.
425
+
426
+    # Set chart start time, height, and width.
427
+    strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \
428
+             % (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT)
429
+   
430
+    # Set the range and scaling of the chart y-axis.
431
+    if lower < upper:
432
+        strCmd  +=  "-l %s -u %s -r " % (lower, upper)
433
+    elif autoScale:
434
+        strCmd += "-A "
435
+    strCmd += "-Y "
436
+
437
+    # Set the chart ordinate label and chart title. 
438
+    strCmd += "-v %s -t %s " % (gLabel, gTitle)
439
+ 
440
+    # Show the data, or a moving average trend line over
441
+    # the data, or both.
442
+    strCmd += "DEF:dSeries=%s:%s:LAST " % (_RRD_FILE, dataItem)
443
+    if addTrend == 0:
444
+        strCmd += "LINE1:dSeries#0400ff "
445
+    elif addTrend == 1:
446
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
447
+                  % trendWindow[gStart]
448
+    elif addTrend == 2:
449
+        strCmd += "LINE1:dSeries#0400ff "
450
+        strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \
451
+                  % trendWindow[gStart]
452
+     
453
+    if verboseDebug:
454
+        print "\n%s" % strCmd # DEBUG
455
+    
456
+    # Run the formatted rrdtool command as a subprocess.
457
+    try:
458
+        result = subprocess.check_output(strCmd, \
459
+                     stderr=subprocess.STDOUT,   \
460
+                     shell=True)
461
+    except subprocess.CalledProcessError, exError:
462
+        print "rrdtool graph failed: %s" % (exError.output)
463
+        return False
464
+
465
+    if debugOption:
466
+        print "rrdtool graph: %s" % result,
467
+    return True
468
+
469
+##end def
470
+
471
+def generateGraphs():
472
+    """Generate graphs for display in html documents.
473
+       Parameters: none
474
+       Returns: nothing
475
+    """
476
+    autoScale = False
477
+
478
+    createGraph('24hr_signal', 'S', 'dBm', 
479
+                'RSSI\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
480
+    createGraph('24hr_noise', 'N', 'dBm', 
481
+                'Noise\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
482
+    createGraph('24hr_snr', 'SNR', 'dB', 
483
+                'SNR\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
484
+    createGraph('24hr_rx_rate', 'RX_RATE', 'Mbps',
485
+                'Rx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
486
+    createGraph('24hr_tx_rate', 'TX_RATE', 'Mbps',
487
+                'Tx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
488
+    #createGraph('24hr_rx_mcs', 'RX_MCS', 'Index',
489
+    #            'Rx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
490
+    #createGraph('24hr_tx_mcs', 'TX_MCS', 'Index',
491
+    #            'Tx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale)
492
+
493
+    createGraph('4wk_signal', 'S', 'dBm', 
494
+                'RSSI\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
495
+    createGraph('4wk_noise', 'N', 'dBm', 
496
+                'Noise\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
497
+    createGraph('4wk_snr', 'SNR', 'dB', 
498
+                'SNR\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
499
+    createGraph('4wk_rx_rate', 'RX_RATE', 'Mbps',
500
+                'Rx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
501
+    createGraph('4wk_tx_rate', 'TX_RATE', 'Mbps',
502
+                'Tx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
503
+    #createGraph('4wk_rx_mcs', 'RX_MCS', 'Index',
504
+    #            'Rx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
505
+    #createGraph('4wk_tx_mcs', 'TX_MCS', 'Index',
506
+    #            'Tx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale)
507
+
508
+    createGraph('12m_signal', 'S', 'dBm', 
509
+                'RSSI\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
510
+    createGraph('12m_noise', 'N', 'dBm', 
511
+                'Noise\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
512
+    createGraph('12m_snr', 'SNR', 'dB', 
513
+                'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
514
+    createGraph('12m_rx_rate', 'RX_RATE', 'Mbps',
515
+                'Rx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
516
+    createGraph('12m_tx_rate', 'TX_RATE', 'Mbps',
517
+                'Tx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
518
+    #createGraph('12m_rx_mcs', 'RX_MCS', 'Index',
519
+    #            'Rx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
520
+    #createGraph('12m_tx_mcs', 'TX_MCS', 'Index',
521
+    #            'Tx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale)
522
+##end def
523
+
524
+def getCLarguments():
525
+    """Get command line arguments.  There are four possible arguments
526
+          -d turns on debug mode
527
+          -v turns on verbose debug mode
528
+          -t sets the aredn node query interval
529
+          -u sets the url of the aredn nodeing device
530
+       Returns: nothing
531
+    """
532
+    global debugOption, verboseDebug, dataRequestInterval, \
533
+           arednNodeUrl
534
+
535
+    index = 1
536
+    while index < len(sys.argv):
537
+        if sys.argv[index] == '-d':
538
+            debugOption = True
539
+        elif sys.argv[index] == '-v':
540
+            debugOption = True
541
+            verboseDebug = True
542
+        elif sys.argv[index] == '-p':
543
+            try:
544
+                dataRequestInterval = abs(int(sys.argv[index + 1]))
545
+            except:
546
+                print "invalid polling period"
547
+                exit(-1)
548
+            index += 1
549
+        elif sys.argv[index] == '-u':
550
+            arednNodeUrl = sys.argv[index + 1]
551
+            index += 1
552
+        else:
553
+            cmd_name = sys.argv[0].split('/')
554
+            print "Usage: %s [-d] [-v] [-pt seconds] [-u url}" % cmd_name[-1]
555
+            exit(-1)
556
+        index += 1
557
+##end def
558
+
559
+def main():
560
+    """Handles timing of events and acts as executive routine managing
561
+       all other functions.
562
+       Parameters: none
563
+       Returns: nothing
564
+    """
565
+    global dataRequestInterval
566
+
567
+    signal.signal(signal.SIGTERM, terminateAgentProcess)
568
+
569
+    print '%s starting up arednsig agent process' % \
570
+                  (getTimeStamp())
571
+
572
+    # last time output JSON file updated
573
+    lastDataRequestTime = -1
574
+    # last time charts generated
575
+    lastChartUpdateTime = - 1
576
+    # last time the rrdtool database updated
577
+    lastDatabaseUpdateTime = -1
578
+
579
+    ## Get command line arguments.
580
+    getCLarguments()
581
+
582
+    requestIntervalSeconds = dataRequestInterval * 60 # convert to seconds
583
+
584
+    chartUpdateInterval = dataRequestInterval # get charts when updating database
585
+
586
+    ## Exit with error if rrdtool database does not exist.
587
+    if not os.path.exists(_RRD_FILE):
588
+        print 'rrdtool database does not exist\n' \
589
+              'use createRadmonRrd script to ' \
590
+              'create rrdtool database\n'
591
+        exit(1)
592
+ 
593
+    ## main loop
594
+    while True:
595
+
596
+        currentTime = time.time() # get current time in seconds
597
+
598
+        # Every web update interval request data from the aredn
599
+        # node and process the received data.
600
+        if currentTime - lastDataRequestTime > requestIntervalSeconds:
601
+            lastDataRequestTime = currentTime
602
+            ldData = []
603
+            result = True
604
+
605
+            # Get the data string from the device.
606
+            sData = getArednNodeData()
607
+            # If the first http request fails, try one more time.
608
+            if sData == None:
609
+                time.sleep(5)
610
+                sData = getArednNodeData()
611
+                if sData == None:
612
+                    result = False
613
+
614
+            # If successful parse the data.
615
+            if result:
616
+                result = parseNodeData(sData, ldData)
617
+           
618
+            # If parsing successful, convert the data.
619
+            if result:
620
+                result = convertData(ldData)
621
+
622
+            # If conversion successful, write data to data files.
623
+            if result:
624
+                result = updateDatabase(ldData)
625
+
626
+            if result:
627
+                writeOutputDataFile(sData, ldData)
628
+
629
+            # Set the node status to online or offline depending on the
630
+            # success or failure of the above operations.
631
+            setNodeStatus(result)
632
+
633
+
634
+        # At the chart generation interval, generate charts.
635
+        if currentTime - lastChartUpdateTime > chartUpdateInterval:
636
+            lastChartUpdateTime = currentTime
637
+            p = multiprocessing.Process(target=generateGraphs, args=())
638
+            p.start()
639
+
640
+        # Relinquish processing back to the operating system until
641
+        # the next update interval.
642
+
643
+        elapsedTime = time.time() - currentTime
644
+        if debugOption:
645
+            if result:
646
+                print "%s update successful:" % getTimeStamp(),
647
+            else:
648
+               print "%s update failed:" % getTimeStamp(),
649
+            print "%6f seconds processing time\n" % elapsedTime 
650
+        remainingTime = requestIntervalSeconds - elapsedTime
651
+        if remainingTime > 0.0:
652
+            time.sleep(remainingTime)
653
+    ## end while
654
+    return
655
+## end def
656
+
657
+if __name__ == '__main__':
658
+    try:
659
+        main()
660
+    except KeyboardInterrupt:
661
+        print '\n',
662
+        terminateAgentProcess('KeyboardInterrupt','Module')