Browse code

minor revisions

Gandolf authored on 06/21/2021 19:58:00
Showing 3 changed files
... ...
@@ -5,13 +5,11 @@
5 5
 # whether your local network uses a domain name server.
6 6
 #
7 7
 
8
-APP_PATH="/home/$USER/bin"
9
-LOG_PATH="/home/$USER/log"
10
-
11 8
 AGENT_NAME="[a]rednsigAgent.py"
9
+SOURCE_URL="http://localnode.local.mesh/cgi-bin/status"
12 10
 
13
-#NODE_URL="http://localnode:8080/cgi-bin/signal.json"
14
-NODE_URL="http://192.168.1.30:8080/cgi-bin/status"
11
+APP_PATH="/home/$USER/bin"
12
+LOG_PATH="/home/$USER/log"
15 13
 
16 14
 # Changing the value overides the default data request interval hardcoded
17 15
 # in the agent script.  '60' is the default. Value is in seconds.
... ...
@@ -26,7 +24,11 @@ if [ -n "$PROCESS_ID" ]; then
26 24
 else
27 25
   printf "starting up arednsig agent\n"
28 26
   cd $APP_PATH
29
-  $(./$AGENT_NAME -u $NODE_URL -p $POLLING_INTERVAL >> \
30
- $LOG_PATH/arednsigAgent.log 2>&1 &)
27
+  if [ "$1" != "" ]; then
28
+    ./$AGENT_NAME $1 -u $SOURCE_URL -p $POLLING_INTERVAL
29
+  else
30
+    ./$AGENT_NAME -u $SOURCE_URL -p $POLLING_INTERVAL >> \
31
+      $LOG_PATH/arednsigAgent.log 2>&1 &
32
+  fi
31 33
 fi
32 34
 
... ...
@@ -1,5 +1,5 @@
1 1
 #!/bin/bash
2
-# Stop the radmon agent process and clean up environment.
2
+# Stop the arednsig agent process.
3 3
 
4 4
 AGENT_NAME="[a]rednsigAgent.py"
5 5
 
... ...
@@ -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
+