Browse code

2017-11-27 update

fractalxaos authored on 11/28/2017 01:24:55
Showing 2 changed files
1 1
Binary files a/DIY Radmon Project Description.pdf and b/DIY Radmon Project Description.pdf differ
... ...
@@ -1,18 +1,19 @@
1 1
 #!/usr/bin/python -u
2
-## The -u option above turns off block buffering of python output. This assures
3
-## that each error message gets individually printed to the log file.
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 4
 #
5 5
 # Module: radmonAgent.py
6 6
 #
7
-# Description: This module acts as an agent between the radiation monitoring device
8
-# and the Internet web server.  The agent periodically sends an http request to the
9
-# radiation monitoring device and processes the response from the device and performs
10
-# a number of operations:
7
+# Description: This module acts as an agent between the radiation monitoring
8
+# device and the Internet web server.  The agent periodically sends an http
9
+# request to the radiation monitoring device and processes the response from
10
+# the device and performs a number of operations:
11 11
 #     - conversion of data items
12 12
 #     - update a round robin (rrdtool) database with the radiation data
13 13
 #     - periodically generate graphic charts for display in html documents
14 14
 #     - forward the radiation data to other services
15
-#     - write the processed weather data to a JSON file for use by html documents
15
+#     - write the processed weather data to a JSON file for use by html
16
+#       documents
16 17
 #
17 18
 # Copyright 2015 Jeff Owrey
18 19
 #    This program is free software: you can redistribute it and/or modify
... ...
@@ -29,9 +30,12 @@
29 30
 #    along with this program.  If not, see http://www.gnu.org/license.
30 31
 #
31 32
 # Revision History
32
-#   * v20 released 15 Sep 2015 by J L Owrey
33
+#   * v20 released 15 Sep 2015 by J L Owrey; first release
34
+#   * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates
33 35
 #
34 36
 
37
+_MIRROR_SERVER = True
38
+
35 39
 import os
36 40
 import urllib2
37 41
 import sys   
... ...
@@ -44,32 +48,54 @@ _USER = os.environ['USER']
44 48
 
45 49
    ### DEFAULT WEATHER STATION URL ###
46 50
 
47
-_DEFAULT_RADIATION_MONITOR_URL = "{your weather station url}"
48
-_DATA_FORWARDING_FILE = "/home/%s/public_html/radmon/dynamic/rad.dat" % _USER
51
+if _MIRROR_SERVER:
52
+    _DEFAULT_RADIATION_MONITOR_URL = \
53
+        "http://73.157.139.23:7361/~pi/radmon/dynamic/rad.dat"
54
+else:
55
+    _DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.8"
49 56
 
50 57
     ### FILE AND FOLDER LOCATIONS ###
51 58
 
52
-_TMP_DIRECTORY = "/tmp/radmon" # folder for charts and output data file
53
-_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER # database that stores the data
54
-_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs
59
+# folder for containing dynamic data objects
60
+_DYNAMIC_FOLDER_PATH = "/home/%s/public_html/radmon/dynamic/" % _USER
61
+# folder for charts and output data file
62
+_CHARTS_DIRECTORY = _DYNAMIC_FOLDER_PATH
63
+ # database that stores weather data
64
+_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER
65
+# location of data output file
66
+_OUTPUT_DATA_FILE = _DYNAMIC_FOLDER_PATH + "radmonData.js"
67
+# location of data forwarding file
68
+_DATA_FORWARDING_FILE = _DYNAMIC_FOLDER_PATH + "rad.dat"
55 69
 
56 70
     ### GLOBAL CONSTANTS ###
57
-
58
-_DEFAULT_DATA_REQUEST_INTERVAL = 10 # interval between data requests to radiation monitor
59
-_CHART_UPDATE_INTERVAL = 300 # defines how often the charts get updated in seconds
60
-_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated
61
-_HTTP_REQUEST_TIMEOUT = 3 # number seconds to wait for a response to HTTP request
62
-_MAX_RADIATION_MONITOR_OFFLINE_COUNT = 2 # max number of failed data requests allowed
71
+# interval in seconds between data requests to radiation monitor
72
+_DEFAULT_DATA_REQUEST_INTERVAL = 5
73
+# defines how often the charts get updated in seconds
74
+_CHART_UPDATE_INTERVAL = 300
75
+# defines how often the database gets updated
76
+_DATABASE_UPDATE_INTERVAL = 30
77
+# number seconds to wait for a response to HTTP request
78
+_HTTP_REQUEST_TIMEOUT = 3
79
+# max number of failed data requests allowed
80
+_MAX_RADIATION_MONITOR_OFFLINE_COUNT = 2
81
+# radmon chart dimensions
63 82
 _CHART_WIDTH = 600
64 83
 _CHART_HEIGHT = 150
65 84
 
66 85
    ### GLOBAL VARIABLES ###
67 86
 
87
+# turn on or off of verbose debugging information
68 88
 debugOption = False
89
+# online status of radiation monitor
69 90
 radiationMonitorOnline = True
91
+# number of unsuccessful http requests
70 92
 radiationMonitorOfflineCount = 0
71
-dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL  # web update frequency
72
-radiationMonitorUrl = _DEFAULT_RADIATION_MONITOR_URL  # radiation monitor network address
93
+# status of reset command to radiation monitor
94
+remoteDeviceReset = False
95
+# web update frequency
96
+dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
97
+# radiation monitor network address
98
+radiationMonitorUrl = _DEFAULT_RADIATION_MONITOR_URL
73 99
 
74 100
 
75 101
   ###  PRIVATE METHODS  ###
... ...
@@ -101,14 +127,13 @@ def setOfflineStatus(dData):
101 127
     # that we are now offline.
102 128
     if radiationMonitorOnline:
103 129
         print "%s: radiation monitor offline" % getTimeStamp()
130
+        if os.path.exists(_DATA_FORWARDING_FILE):
131
+            os.remove(_DATA_FORWARDING_FILE)
104 132
         radiationMonitorOnline = False
105 133
 
106
-    # Set data items to blank.
107
-    dData['UTC'] = ''
108
-    dData['CPM'] = ''
109
-    dData['CPS'] = ''
110
-    dData['uSvPerHr'] = ''
111
-    dData['Mode'] = ''
134
+    for key in dData:
135
+        dData[key] = ''
136
+
112 137
     dData['status'] = 'offline'
113 138
 
114 139
     writeOutputDataFile(dData)
... ...
@@ -128,11 +153,20 @@ def getRadiationData():
128 153
     Returns a string containing the radiation data, or None if
129 154
     not successful.
130 155
     """
131
-    global radiationMonitorOnline, radiationMonitorOfflineCount
156
+    global radiationMonitorOnline, radiationMonitorOfflineCount, \
157
+           remoteDeviceReset
158
+
159
+    sUrl = radiationMonitorUrl
160
+
161
+    if not _MIRROR_SERVER:
162
+        if remoteDeviceReset:
163
+            sUrl += "/reset"
164
+            remoteDeviceReset = False
165
+        else:
166
+            sUrl += "/rdata"
132 167
 
133 168
     try:
134
-        conn = urllib2.urlopen(radiationMonitorUrl,
135
-                               timeout=_HTTP_REQUEST_TIMEOUT)
169
+        conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT)
136 170
 
137 171
         # Format received data into a single string.
138 172
         content = ""
... ...
@@ -197,13 +231,26 @@ def convertData(dData):
197 231
     result = True
198 232
  
199 233
     try:
200
-        # Convert UTC from radiation monitoring device to local time.
234
+
235
+        # Uncomment below to use timestamp from radiation monitoring device
236
+        # otherwise the requesting server (this) will generate the
237
+        # timestamp. Allowing the server to generate the timestamp
238
+        # prevents timestamp errors due to the radiation monitoring device
239
+        # failing to synchronize with a NTP time server.
240
+
241
+        #dData['UTC'] = time.time()
242
+
243
+        ## Convert UTC from radiation monitoring device to local time.
201 244
         ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y")
202 245
         local_sec = calendar.timegm(ts_utc)
203 246
         dData['UTC'] = local_sec
204
-
247
+        
205 248
         dData['Mode'] = dData['Mode'].lower()
206
-        dData['uSvPerHr'] = dData.pop('uSv/hr')
249
+ 
250
+        dData['uSvPerHr'] = float(dData.pop('uSv/hr'))
251
+        
252
+        dData['CPM'] = int(dData.pop('CPM'))
253
+
207 254
     except Exception, exError:
208 255
         print "%s convertData: %s" % (getTimeStamp(), exError)
209 256
         result = False
... ...
@@ -243,9 +290,6 @@ def writeOutputDataFile(dData):
243 290
 ## end def
244 291
 
245 292
 def writeForwardingFile(sData):
246
-    """Write weather station response string to a forwarding file for use
247
-       by down stream servers that mirror this site.
248
-    """
249 293
     # Write the string to the output data file for use by html documents.
250 294
     try:
251 295
         fc = open(_DATA_FORWARDING_FILE, "w")
... ...
@@ -267,8 +311,11 @@ def updateDatabase(dData):
267 311
                         written to the rr database file
268 312
     Returns true if successful, false otherwise.
269 313
     """
270
-    # The RR database stores whole units, so convert uSv to Sv.   
271
-    Svvalue = float(dData['uSvPerHr']) * 1.0E-06 # convert micro-Sieverts to Sieverts
314
+    global remoteDeviceReset
315
+
316
+    # The RR database stores whole units, so convert uSv to Sv.
317
+    #Svvalue = float(dData['uSvPerHr']) * 1.0E-06
318
+    Svvalue = dData['uSvPerHr'] * 1.0E-06 
272 319
 
273 320
     # Create the rrdtool update command.
274 321
     strCmd = "rrdtool update %s %s:%s:%s" % \
... ...
@@ -283,6 +330,11 @@ def updateDatabase(dData):
283 330
     except subprocess.CalledProcessError, exError:
284 331
         print "%s: rrdtool update failed: %s" % \
285 332
                                  (getTimeStamp(), exError.output)
333
+        if exError.output.find("illegal attempt to update using time") > -1:
334
+            remoteDeviceReset = True
335
+            print "%s: rebooting radiation monitor" % \
336
+                                 (getTimeStamp())
337
+
286 338
         return False
287 339
 
288 340
     return True
... ...
@@ -306,7 +358,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart,
306 358
                lower and upper parameters to set vertical axis scale
307 359
        Returns true if successful, false otherwise.
308 360
     """
309
-    gPath = _TMP_DIRECTORY + '/' + fileName + ".png"
361
+    gPath = _CHARTS_DIRECTORY + fileName + ".png"
310 362
     trendWindow = { 'end-1day': 7200,
311 363
                     'end-4weeks': 172800,
312 364
                     'end-12months': 604800 }
... ...
@@ -410,24 +462,25 @@ def getCLarguments():
410 462
 ##end def
411 463
 
412 464
 def main():
413
-    """Handles timing of events and acts as executive routine managing all other
414
-       functions.
465
+    """Handles timing of events and acts as executive routine managing
466
+       all other functions.
415 467
        Parameters: none
416 468
        Returns nothing.
417 469
     """
418 470
 
419
-    lastDataRequestTime = -1 # last time output JSON file updated
420
-    lastChartUpdateTime = - 1 # last time charts generated
421
-    lastDatabaseUpdateTime = -1 # last time the rrdtool database updated
422
-    dData = {}  # dictionary object for temporary data storage
471
+    # last time output JSON file updated
472
+    lastDataRequestTime = -1
473
+    # last time charts generated
474
+    lastChartUpdateTime = - 1
475
+    # last time the rrdtool database updated
476
+    lastDatabaseUpdateTime = -1
477
+
478
+    # define empty dictionary object for radmon data
479
+    dData = {}
423 480
 
424 481
     ## Get command line arguments.
425 482
     getCLarguments()
426 483
 
427
-    ## Create www data folder if it does not already exist.
428
-    if not os.path.isdir(_TMP_DIRECTORY):
429
-        os.makedirs(_TMP_DIRECTORY)
430
-
431 484
     ## Exit with error if rrdtool database does not exist.
432 485
     if not os.path.exists(_RRD_FILE):
433 486
         print "cannot find rrdtool database\nuse createWeatherRrd script to" \
... ...
@@ -453,6 +506,7 @@ def main():
453 506
 
454 507
             # If successful parse the data.
455 508
             if result:
509
+                dData = {}
456 510
                 result = parseDataString(sData, dData)
457 511
 
458 512
             # If parsing successful, convert the data.
... ...
@@ -467,7 +521,8 @@ def main():
467 521
                     print "http request successful"
468 522
 
469 523
                 # At the rrdtool database update interval, update the database.
470
-                if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL:   
524
+                if currentTime - lastDatabaseUpdateTime > \
525
+                        _DATABASE_UPDATE_INTERVAL:   
471 526
                     lastDatabaseUpdateTime = currentTime
472 527
                     ## Update the round robin database with the parsed data.
473 528
                     result = updateDatabase(dData)