... | ... |
@@ -36,6 +36,8 @@ |
36 | 36 |
# * v23 released 16 Nov 2018 by J L Owrey: improved fault handling |
37 | 37 |
# and data conversion |
38 | 38 |
# * v24 released 14 Jun 2021 by J L Owrey; minor revisions |
39 |
+# * v25 released 9 Jul 2021 by J L Owrey; improved handling of |
|
40 |
+# monitor status function |
|
39 | 41 |
# |
40 | 42 |
#2345678901234567890123456789012345678901234567890123456789012345678901234567890 |
41 | 43 |
|
... | ... |
@@ -58,7 +60,7 @@ _USE_RADMON_TIMESTAMP = True |
58 | 60 |
### DEFAULT RADIATION MONITOR URL ### |
59 | 61 |
|
60 | 62 |
_DEFAULT_RADIATION_MONITOR_URL = \ |
61 |
- "{your radiation monitor url}" |
|
63 |
+ "http://192.168.1.24" |
|
62 | 64 |
|
63 | 65 |
### FILE AND FOLDER LOCATIONS ### |
64 | 66 |
|
... | ... |
@@ -100,7 +102,7 @@ debugMode = False |
100 | 102 |
# count of failed attempts to get data from radiation monitor |
101 | 103 |
failedUpdateCount = 0 |
102 | 104 |
# detected status of radiation monitor device |
103 |
-radmonOnline = True |
|
105 |
+radmonOnline = False |
|
104 | 106 |
|
105 | 107 |
# status of reset command to radiation monitor |
106 | 108 |
remoteDeviceReset = False |
... | ... |
@@ -147,11 +149,9 @@ def terminateAgentProcess(signal, frame): |
147 | 149 |
signal, frame - dummy parameters |
148 | 150 |
Returns: nothing |
149 | 151 |
""" |
150 |
- # Inform downstream clients by removing output data file. |
|
151 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
152 |
- os.remove(_OUTPUT_DATA_FILE) |
|
153 | 152 |
print('%s terminating radmon agent process' % \ |
154 | 153 |
(getTimeStamp())) |
154 |
+ setStatusToOffline() |
|
155 | 155 |
sys.exit(0) |
156 | 156 |
##end def |
157 | 157 |
|
... | ... |
@@ -174,12 +174,8 @@ def getRadiationData(dData): |
174 | 174 |
|
175 | 175 |
try: |
176 | 176 |
currentTime = time.time() |
177 |
- |
|
178 | 177 |
response = urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
179 |
- |
|
180 |
- if verboseMode: |
|
181 |
- requestTime = time.time() - currentTime |
|
182 |
- print("http request: %.4f seconds" % requestTime) |
|
178 |
+ requestTime = time.time() - currentTime |
|
183 | 179 |
|
184 | 180 |
content = response.read().decode('utf-8') |
185 | 181 |
content = content.replace('\n', '') |
... | ... |
@@ -198,9 +194,10 @@ def getRadiationData(dData): |
198 | 194 |
|
199 | 195 |
if debugMode: |
200 | 196 |
print(content) |
197 |
+ if verboseMode: |
|
198 |
+ print("http request successful: %.4f sec" % requestTime) |
|
201 | 199 |
|
202 | 200 |
dData['content'] = content |
203 |
- |
|
204 | 201 |
return True |
205 | 202 |
##end def |
206 | 203 |
|
... | ... |
@@ -221,17 +218,19 @@ def parseDataString(dData): |
221 | 218 |
print("%s parseDataString: %s" % (getTimeStamp(), exError)) |
222 | 219 |
return False |
223 | 220 |
|
221 |
+ # Verfy the expected number of data items have been received. |
|
222 |
+ if len(lData) != 5: |
|
223 |
+ print("%s parse failed: corrupted data string" % getTimeStamp()) |
|
224 |
+ return False; |
|
225 |
+ |
|
224 | 226 |
# Load the parsed data into a dictionary for easy access. |
225 | 227 |
for item in lData: |
226 | 228 |
if "=" in item: |
227 | 229 |
dData[item.split('=')[0]] = item.split('=')[1] |
230 |
+ |
|
228 | 231 |
# Add status to dictionary object |
229 | 232 |
dData['status'] = 'online' |
230 |
- |
|
231 |
- # Verfy the expected number of data items have been received. |
|
232 |
- if len(dData) != 6: |
|
233 |
- print("%s parse failed: corrupted data string" % getTimeStamp()) |
|
234 |
- return False; |
|
233 |
+ dData['serverMode'] = _SERVER_MODE |
|
235 | 234 |
|
236 | 235 |
return True |
237 | 236 |
##end def |
... | ... |
@@ -278,18 +277,11 @@ def writeOutputFile(dData): |
278 | 277 |
to the output data file |
279 | 278 |
Returns: True if successful, False otherwise |
280 | 279 |
""" |
281 |
- # Create temporary copy of output data dictionary |
|
282 |
- # and remove unnecessary items. |
|
283 |
- dTemp = dict(dData) |
|
284 |
- dTemp.pop('ELT') |
|
285 |
- dTemp.pop('UTC') |
|
286 |
- |
|
287 | 280 |
# Format the radmon data as string using java script object notation. |
288 | 281 |
jsData = json.loads("{}") |
289 | 282 |
try: |
290 |
- for key in dTemp: |
|
291 |
- jsData.update({key:dTemp[key]}) |
|
292 |
- jsData.update({"serverMode":"%s" % _SERVER_MODE }) |
|
283 |
+ for key in dData: |
|
284 |
+ jsData.update({key:dData[key]}) |
|
293 | 285 |
sData = "[%s]" % json.dumps(jsData) |
294 | 286 |
except Exception as exError: |
295 | 287 |
print("%s writeOutputFile: %s" % (getTimeStamp(), exError)) |
... | ... |
@@ -310,6 +302,35 @@ def writeOutputFile(dData): |
310 | 302 |
return True |
311 | 303 |
## end def |
312 | 304 |
|
305 |
+def setRadmonStatus(updateSuccess): |
|
306 |
+ """Detect if radiation monitor is offline or not available on |
|
307 |
+ the network. After a set number of attempts to get data |
|
308 |
+ from the monitor set a flag that the radmon is offline. |
|
309 |
+ Parameters: |
|
310 |
+ updateSuccess - a boolean that is True if data request |
|
311 |
+ successful, False otherwise |
|
312 |
+ Returns: nothing |
|
313 |
+ """ |
|
314 |
+ global failedUpdateCount, radmonOnline |
|
315 |
+ |
|
316 |
+ if updateSuccess: |
|
317 |
+ failedUpdateCount = 0 |
|
318 |
+ # Set status and send a message to the log if the device |
|
319 |
+ # previously offline and is now online. |
|
320 |
+ if not radmonOnline: |
|
321 |
+ print('%s radiation monitor online' % getTimeStamp()) |
|
322 |
+ radmonOnline = True |
|
323 |
+ return |
|
324 |
+ elif failedUpdateCount == _MAX_FAILED_DATA_REQUESTS - 1: |
|
325 |
+ # Max number of failed data requests, so set |
|
326 |
+ # device status to offline. |
|
327 |
+ setStatusToOffline() |
|
328 |
+ ## end if |
|
329 |
+ failedUpdateCount += 1 |
|
330 |
+##end def |
|
331 |
+ |
|
332 |
+ ### DATABASE FUNCTIONS ### |
|
333 |
+ |
|
313 | 334 |
def updateDatabase(dData): |
314 | 335 |
""" |
315 | 336 |
Update the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -343,40 +364,11 @@ def updateDatabase(dData): |
343 | 364 |
return False |
344 | 365 |
|
345 | 366 |
if verboseMode and not debugMode: |
346 |
- print("update database") |
|
367 |
+ print("database update successful") |
|
347 | 368 |
|
348 | 369 |
return True |
349 | 370 |
##end def |
350 | 371 |
|
351 |
-def setRadmonStatus(updateSuccess): |
|
352 |
- """Detect if radiation monitor is offline or not available on |
|
353 |
- the network. After a set number of attempts to get data |
|
354 |
- from the monitor set a flag that the radmon is offline. |
|
355 |
- Parameters: |
|
356 |
- updateSuccess - a boolean that is True if data request |
|
357 |
- successful, False otherwise |
|
358 |
- Returns: nothing |
|
359 |
- """ |
|
360 |
- global failedUpdateCount, radmonOnline |
|
361 |
- |
|
362 |
- if updateSuccess: |
|
363 |
- failedUpdateCount = 0 |
|
364 |
- # Set status and send a message to the log if the radmon was |
|
365 |
- # previously offline and is now online. |
|
366 |
- if not radmonOnline: |
|
367 |
- print('%s radiation monitor online' % getTimeStamp()) |
|
368 |
- radmonOnline = True |
|
369 |
- else: |
|
370 |
- # The last attempt failed, so update the failed attempts |
|
371 |
- # count. |
|
372 |
- failedUpdateCount += 1 |
|
373 |
- |
|
374 |
- if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
|
375 |
- # Max number of failed data requests, so set |
|
376 |
- # monitor status to offline. |
|
377 |
- setStatusToOffline() |
|
378 |
-##end def |
|
379 |
- |
|
380 | 372 |
def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
381 | 373 |
lower, upper, addTrend, autoScale): |
382 | 374 |
"""Uses rrdtool to create a graph of specified radmon data item. |
... | ... |
@@ -514,6 +506,7 @@ def main(): |
514 | 506 |
signal.signal(signal.SIGTERM, terminateAgentProcess) |
515 | 507 |
signal.signal(signal.SIGINT, terminateAgentProcess) |
516 | 508 |
|
509 |
+ print('===================') |
|
517 | 510 |
print('%s starting up radmon agent process' % \ |
518 | 511 |
(getTimeStamp())) |
519 | 512 |
|
... | ... |
@@ -1,4 +1,4 @@ |
1 |
-#!/usr/bin/python -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 |
# |
... | ... |
@@ -35,23 +35,30 @@ |
35 | 35 |
# improved radmon device offline status handling |
36 | 36 |
# * v23 released 16 Nov 2018 by J L Owrey: improved fault handling |
37 | 37 |
# and data conversion |
38 |
+# * v24 released 14 Jun 2021 by J L Owrey; minor revisions |
|
39 |
+# |
|
38 | 40 |
#2345678901234567890123456789012345678901234567890123456789012345678901234567890 |
39 | 41 |
|
40 | 42 |
import os |
41 |
-import urllib2 |
|
42 | 43 |
import sys |
43 | 44 |
import signal |
44 | 45 |
import subprocess |
45 | 46 |
import multiprocessing |
46 | 47 |
import time |
47 | 48 |
import calendar |
49 |
+import json |
|
50 |
+from urllib.request import urlopen |
|
51 |
+ |
|
52 |
+ ### ENVIRONMENT ### |
|
48 | 53 |
|
49 | 54 |
_USER = os.environ['USER'] |
55 |
+_SERVER_MODE = "primary" |
|
56 |
+_USE_RADMON_TIMESTAMP = True |
|
50 | 57 |
|
51 | 58 |
### DEFAULT RADIATION MONITOR URL ### |
52 | 59 |
|
53 |
-# ip address of radiation monitoring device |
|
54 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
60 |
+_DEFAULT_RADIATION_MONITOR_URL = \ |
|
61 |
+ "{your radiation monitor url}" |
|
55 | 62 |
|
56 | 63 |
### FILE AND FOLDER LOCATIONS ### |
57 | 64 |
|
... | ... |
@@ -60,42 +67,40 @@ _DOCROOT_PATH = "/home/%s/public_html/radmon/" % _USER |
60 | 67 |
# folder for charts and output data file |
61 | 68 |
_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
62 | 69 |
# location of data output file |
63 |
-_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
|
70 |
+_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonData.js" |
|
64 | 71 |
# database that stores radmon data |
65 | 72 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
66 | 73 |
|
67 | 74 |
### GLOBAL CONSTANTS ### |
68 | 75 |
|
69 | 76 |
# max number of failed data requests allowed |
70 |
-_MAX_FAILED_DATA_REQUESTS = 2 |
|
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 |
+_MAX_FAILED_DATA_REQUESTS = 3 |
|
78 |
+# interval in seconds between data requests |
|
79 |
+_DEFAULT_DATA_REQUEST_INTERVAL = 2 |
|
77 | 80 |
# number seconds to wait for a response to HTTP request |
78 | 81 |
_HTTP_REQUEST_TIMEOUT = 3 |
82 |
+ |
|
83 |
+# interval in seconds between database updates |
|
84 |
+_DATABASE_UPDATE_INTERVAL = 30 |
|
85 |
+# interval in seconds between chart updates |
|
86 |
+_CHART_UPDATE_INTERVAL = 300 |
|
79 | 87 |
# standard chart width in pixels |
80 | 88 |
_CHART_WIDTH = 600 |
81 | 89 |
# standard chart height in pixels |
82 | 90 |
_CHART_HEIGHT = 150 |
83 |
-# source of time stamp attached to output data file |
|
84 |
-_USE_RADMON_TIMESTAMP = True |
|
85 | 91 |
|
86 | 92 |
### GLOBAL VARIABLES ### |
87 | 93 |
|
88 | 94 |
# turn on or off of verbose debugging information |
89 |
-debugOption = False |
|
90 |
-verboseDebug = False |
|
95 |
+verboseMode = False |
|
96 |
+debugMode = False |
|
91 | 97 |
|
92 | 98 |
# The following two items are used for detecting system faults |
93 | 99 |
# and radiation monitor online or offline status. |
94 |
- |
|
95 | 100 |
# count of failed attempts to get data from radiation monitor |
96 | 101 |
failedUpdateCount = 0 |
97 | 102 |
# detected status of radiation monitor device |
98 |
-stationOnline = True |
|
103 |
+radmonOnline = True |
|
99 | 104 |
|
100 | 105 |
# status of reset command to radiation monitor |
101 | 106 |
remoteDeviceReset = False |
... | ... |
@@ -122,16 +127,16 @@ def setStatusToOffline(): |
122 | 127 |
Parameters: none |
123 | 128 |
Returns: nothing |
124 | 129 |
""" |
125 |
- global stationOnline |
|
130 |
+ global radmonOnline |
|
126 | 131 |
|
127 | 132 |
# Inform downstream clients by removing output data file. |
128 | 133 |
if os.path.exists(_OUTPUT_DATA_FILE): |
129 | 134 |
os.remove(_OUTPUT_DATA_FILE) |
130 | 135 |
# If the radiation monitor was previously online, then send |
131 | 136 |
# a message that we are now offline. |
132 |
- if stationOnline: |
|
133 |
- print '%s radiation monitor offline' % getTimeStamp() |
|
134 |
- stationOnline = False |
|
137 |
+ if radmonOnline: |
|
138 |
+ print('%s radiation monitor offline' % getTimeStamp()) |
|
139 |
+ radmonOnline = False |
|
135 | 140 |
##end def |
136 | 141 |
|
137 | 142 |
def terminateAgentProcess(signal, frame): |
... | ... |
@@ -145,14 +150,14 @@ def terminateAgentProcess(signal, frame): |
145 | 150 |
# Inform downstream clients by removing output data file. |
146 | 151 |
if os.path.exists(_OUTPUT_DATA_FILE): |
147 | 152 |
os.remove(_OUTPUT_DATA_FILE) |
148 |
- print '%s terminating radmon agent process' % \ |
|
149 |
- (getTimeStamp()) |
|
153 |
+ print('%s terminating radmon agent process' % \ |
|
154 |
+ (getTimeStamp())) |
|
150 | 155 |
sys.exit(0) |
151 | 156 |
##end def |
152 | 157 |
|
153 | 158 |
### PUBLIC METHODS ### |
154 | 159 |
|
155 |
-def getRadiationData(): |
|
160 |
+def getRadiationData(dData): |
|
156 | 161 |
"""Send http request to radiation monitoring device. The |
157 | 162 |
response from the device contains the radiation data as |
158 | 163 |
unformatted ascii text. |
... | ... |
@@ -160,8 +165,6 @@ def getRadiationData(): |
160 | 165 |
Returns: a string containing the radiation data if successful, |
161 | 166 |
or None if not successful |
162 | 167 |
""" |
163 |
- global remoteDeviceReset |
|
164 |
- |
|
165 | 168 |
sUrl = radiationMonitorUrl |
166 | 169 |
|
167 | 170 |
if remoteDeviceReset: |
... | ... |
@@ -170,49 +173,64 @@ def getRadiationData(): |
170 | 173 |
sUrl += "/rdata" # request data from the monitor |
171 | 174 |
|
172 | 175 |
try: |
173 |
- conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
|
176 |
+ currentTime = time.time() |
|
177 |
+ |
|
178 |
+ response = urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
|
179 |
+ |
|
180 |
+ if verboseMode: |
|
181 |
+ requestTime = time.time() - currentTime |
|
182 |
+ print("http request: %.4f seconds" % requestTime) |
|
174 | 183 |
|
175 |
- # Format received data into a single string. |
|
176 |
- content = "" |
|
177 |
- for line in conn: |
|
178 |
- content += line.strip() |
|
179 |
- del conn |
|
184 |
+ content = response.read().decode('utf-8') |
|
185 |
+ content = content.replace('\n', '') |
|
186 |
+ content = content.replace('\r', '') |
|
187 |
+ if content == "": |
|
188 |
+ raise Exception("empty response") |
|
180 | 189 |
|
181 |
- except Exception, exError: |
|
190 |
+ except Exception as exError: |
|
182 | 191 |
# If no response is received from the device, then assume that |
183 | 192 |
# the device is down or unavailable over the network. In |
184 | 193 |
# that case return None to the calling function. |
185 |
- if debugOption: |
|
186 |
- print "http error: %s" % exError |
|
187 |
- return None |
|
194 |
+ if verboseMode: |
|
195 |
+ print("%s getRadiationData: %s" % (getTimeStamp(), exError)) |
|
196 |
+ return False |
|
197 |
+ ##end try |
|
198 |
+ |
|
199 |
+ if debugMode: |
|
200 |
+ print(content) |
|
201 |
+ |
|
202 |
+ dData['content'] = content |
|
188 | 203 |
|
189 |
- return content |
|
204 |
+ return True |
|
190 | 205 |
##end def |
191 | 206 |
|
192 |
-def parseDataString(sData, dData): |
|
193 |
- """Parse the radiation data JSON string from the radiation |
|
194 |
- monitoring device into its component parts. |
|
207 |
+def parseDataString(dData): |
|
208 |
+ """Parse the data string returned by the radiation monitor |
|
209 |
+ into its component parts. |
|
195 | 210 |
Parameters: |
196 |
- sData - the string containing the data to be parsed |
|
197 |
- dData - a dictionary object to contain the parsed data items |
|
211 |
+ dData - a dictionary object to contain the parsed data items |
|
198 | 212 |
Returns: True if successful, False otherwise |
199 | 213 |
""" |
214 |
+ # Example radiation monitor data string |
|
215 |
+ # $,UTC=17:09:33 6/22/2021,CPS=0,CPM=26,uSv/hr=0.14,Mode=SLOW,# |
|
216 |
+ |
|
200 | 217 |
try: |
201 |
- sTmp = sData[2:-2] |
|
202 |
- lsTmp = sTmp.split(',') |
|
203 |
- except Exception, exError: |
|
204 |
- print "%s parseDataString: %s" % (getTimeStamp(), exError) |
|
218 |
+ sData = dData.pop('content') |
|
219 |
+ lData = sData[2:-2].split(',') |
|
220 |
+ except Exception as exError: |
|
221 |
+ print("%s parseDataString: %s" % (getTimeStamp(), exError)) |
|
205 | 222 |
return False |
206 | 223 |
|
207 | 224 |
# Load the parsed data into a dictionary for easy access. |
208 |
- for item in lsTmp: |
|
225 |
+ for item in lData: |
|
209 | 226 |
if "=" in item: |
210 | 227 |
dData[item.split('=')[0]] = item.split('=')[1] |
228 |
+ # Add status to dictionary object |
|
211 | 229 |
dData['status'] = 'online' |
212 | 230 |
|
213 | 231 |
# Verfy the expected number of data items have been received. |
214 | 232 |
if len(dData) != 6: |
215 |
- print "%s parse failed: corrupted data string" % getTimeStamp() |
|
233 |
+ print("%s parse failed: corrupted data string" % getTimeStamp()) |
|
216 | 234 |
return False; |
217 | 235 |
|
218 | 236 |
return True |
... | ... |
@@ -238,88 +256,60 @@ def convertData(dData): |
238 | 256 |
# that occur when the radiation monitoring device fails to |
239 | 257 |
# synchronize with a valid NTP time server. |
240 | 258 |
dData['ELT'] = time.time() |
241 |
- |
|
242 |
- dData['Mode'] = dData['Mode'].lower() |
|
259 |
+ |
|
260 |
+ dData['date'] = \ |
|
261 |
+ time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
|
262 |
+ dData['mode'] = dData.pop('Mode').lower() |
|
243 | 263 |
dData['uSvPerHr'] = '%.2f' % float(dData.pop('uSv/hr')) |
244 | 264 |
|
245 |
- except Exception, exError: |
|
246 |
- print "%s data conversion failed: %s" % (getTimeStamp(), exError) |
|
265 |
+ except Exception as exError: |
|
266 |
+ print("%s data conversion failed: %s" % (getTimeStamp(), exError)) |
|
247 | 267 |
return False |
248 | 268 |
|
249 | 269 |
return True |
250 | 270 |
##end def |
251 | 271 |
|
252 |
-def writeOutputDataFile(dData): |
|
272 |
+def writeOutputFile(dData): |
|
253 | 273 |
"""Write radiation data items to the output data file, formatted as |
254 |
- a Javascript file. This file may then be accessed and used by |
|
274 |
+ a JSON file. This file may then be accessed and used by |
|
255 | 275 |
by downstream clients, for instance, in HTML documents. |
256 | 276 |
Parameters: |
257 | 277 |
dData - a dictionary object containing the data to be written |
258 | 278 |
to the output data file |
259 | 279 |
Returns: True if successful, False otherwise |
260 | 280 |
""" |
261 |
- # Create temporary copy of output data items. |
|
281 |
+ # Create temporary copy of output data dictionary |
|
282 |
+ # and remove unnecessary items. |
|
262 | 283 |
dTemp = dict(dData) |
263 |
- # Set date to current time and data |
|
264 |
- dTemp['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
|
265 |
- # Remove unnecessary data items. |
|
266 | 284 |
dTemp.pop('ELT') |
267 | 285 |
dTemp.pop('UTC') |
268 | 286 |
|
269 | 287 |
# Format the radmon data as string using java script object notation. |
270 |
- sData = '[{' |
|
271 |
- for key in dTemp: |
|
272 |
- sData += '\"%s\":\"%s\",' % (key, dTemp[key]) |
|
273 |
- sData = sData[:-1] + '}]\n' |
|
288 |
+ jsData = json.loads("{}") |
|
289 |
+ try: |
|
290 |
+ for key in dTemp: |
|
291 |
+ jsData.update({key:dTemp[key]}) |
|
292 |
+ jsData.update({"serverMode":"%s" % _SERVER_MODE }) |
|
293 |
+ sData = "[%s]" % json.dumps(jsData) |
|
294 |
+ except Exception as exError: |
|
295 |
+ print("%s writeOutputFile: %s" % (getTimeStamp(), exError)) |
|
296 |
+ return False |
|
274 | 297 |
|
275 |
- if verboseDebug: |
|
276 |
- print sData, |
|
298 |
+ if debugMode: |
|
299 |
+ print(sData) |
|
277 | 300 |
|
278 | 301 |
# Write the string to the output data file for use by html documents. |
279 | 302 |
try: |
280 | 303 |
fc = open(_OUTPUT_DATA_FILE, "w") |
281 | 304 |
fc.write(sData) |
282 | 305 |
fc.close() |
283 |
- except Exception, exError: |
|
284 |
- print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
|
306 |
+ except Exception as exError: |
|
307 |
+ print("%s writeOutputFile: %s" % (getTimeStamp(), exError)) |
|
285 | 308 |
return False |
286 | 309 |
|
287 | 310 |
return True |
288 | 311 |
## end def |
289 | 312 |
|
290 |
-def setStationStatus(updateSuccess): |
|
291 |
- """Detect if radiation monitor is offline or not available on |
|
292 |
- the network. After a set number of attempts to get data |
|
293 |
- from the monitor set a flag that the station is offline. |
|
294 |
- Parameters: |
|
295 |
- updateSuccess - a boolean that is True if data request |
|
296 |
- successful, False otherwise |
|
297 |
- Returns: nothing |
|
298 |
- """ |
|
299 |
- global failedUpdateCount, stationOnline |
|
300 |
- |
|
301 |
- if updateSuccess: |
|
302 |
- failedUpdateCount = 0 |
|
303 |
- # Set status and send a message to the log if the station was |
|
304 |
- # previously offline and is now online. |
|
305 |
- if not stationOnline: |
|
306 |
- print '%s radiation monitor online' % getTimeStamp() |
|
307 |
- stationOnline = True |
|
308 |
- if debugOption: |
|
309 |
- print 'data request successful' |
|
310 |
- else: |
|
311 |
- # The last attempt failed, so update the failed attempts |
|
312 |
- # count. |
|
313 |
- failedUpdateCount += 1 |
|
314 |
- if debugOption: |
|
315 |
- print 'data request failed' |
|
316 |
- |
|
317 |
- if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
|
318 |
- # Max number of failed data requests, so set |
|
319 |
- # monitor status to offline. |
|
320 |
- setStatusToOffline() |
|
321 |
-##end def |
|
322 |
- |
|
323 | 313 |
def updateDatabase(dData): |
324 | 314 |
""" |
325 | 315 |
Update the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -337,24 +327,54 @@ def updateDatabase(dData): |
337 | 327 |
# Format the rrdtool update command. |
338 | 328 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
339 | 329 |
(_RRD_FILE, dData['ELT'], dData['CPM'], SvPerHr) |
340 |
- if verboseDebug: |
|
341 |
- print "%s" % strCmd # DEBUG |
|
330 |
+ if debugMode: |
|
331 |
+ print("%s" % strCmd) # DEBUG |
|
342 | 332 |
|
343 | 333 |
# Run the command as a subprocess. |
344 | 334 |
try: |
345 | 335 |
subprocess.check_output(strCmd, shell=True, \ |
346 | 336 |
stderr=subprocess.STDOUT) |
347 |
- except subprocess.CalledProcessError, exError: |
|
348 |
- print "%s: rrdtool update failed: %s" % \ |
|
349 |
- (getTimeStamp(), exError.output) |
|
337 |
+ except subprocess.CalledProcessError as exError: |
|
338 |
+ print("%s: rrdtool update failed: %s" % \ |
|
339 |
+ (getTimeStamp(), exError.output)) |
|
350 | 340 |
if exError.output.find("illegal attempt to update using time") > -1: |
351 | 341 |
remoteDeviceReset = True |
352 |
- print "%s: rebooting radiation monitor" % (getTimeStamp()) |
|
342 |
+ print("%s: rebooting radiation monitor" % (getTimeStamp())) |
|
353 | 343 |
return False |
344 |
+ |
|
345 |
+ if verboseMode and not debugMode: |
|
346 |
+ print("update database") |
|
347 |
+ |
|
348 |
+ return True |
|
349 |
+##end def |
|
350 |
+ |
|
351 |
+def setRadmonStatus(updateSuccess): |
|
352 |
+ """Detect if radiation monitor is offline or not available on |
|
353 |
+ the network. After a set number of attempts to get data |
|
354 |
+ from the monitor set a flag that the radmon is offline. |
|
355 |
+ Parameters: |
|
356 |
+ updateSuccess - a boolean that is True if data request |
|
357 |
+ successful, False otherwise |
|
358 |
+ Returns: nothing |
|
359 |
+ """ |
|
360 |
+ global failedUpdateCount, radmonOnline |
|
361 |
+ |
|
362 |
+ if updateSuccess: |
|
363 |
+ failedUpdateCount = 0 |
|
364 |
+ # Set status and send a message to the log if the radmon was |
|
365 |
+ # previously offline and is now online. |
|
366 |
+ if not radmonOnline: |
|
367 |
+ print('%s radiation monitor online' % getTimeStamp()) |
|
368 |
+ radmonOnline = True |
|
354 | 369 |
else: |
355 |
- if debugOption: |
|
356 |
- print 'database update sucessful' |
|
357 |
- return True |
|
370 |
+ # The last attempt failed, so update the failed attempts |
|
371 |
+ # count. |
|
372 |
+ failedUpdateCount += 1 |
|
373 |
+ |
|
374 |
+ if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
|
375 |
+ # Max number of failed data requests, so set |
|
376 |
+ # monitor status to offline. |
|
377 |
+ setStatusToOffline() |
|
358 | 378 |
##end def |
359 | 379 |
|
360 | 380 |
def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
... | ... |
@@ -403,27 +423,27 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
403 | 423 |
if addTrend == 0: |
404 | 424 |
strCmd += "LINE1:dSeries#0400ff " |
405 | 425 |
elif addTrend == 1: |
406 |
- strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
426 |
+ strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \ |
|
407 | 427 |
% trendWindow[gStart] |
408 | 428 |
elif addTrend == 2: |
409 | 429 |
strCmd += "LINE1:dSeries#0400ff " |
410 |
- strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
430 |
+ strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \ |
|
411 | 431 |
% trendWindow[gStart] |
412 | 432 |
|
413 |
- if verboseDebug: |
|
414 |
- print "\n%s" % strCmd # DEBUG |
|
433 |
+ if debugMode: |
|
434 |
+ print("\n%s" % strCmd) # DEBUG |
|
415 | 435 |
|
416 | 436 |
# Run the formatted rrdtool command as a subprocess. |
417 | 437 |
try: |
418 | 438 |
result = subprocess.check_output(strCmd, \ |
419 | 439 |
stderr=subprocess.STDOUT, \ |
420 | 440 |
shell=True) |
421 |
- except subprocess.CalledProcessError, exError: |
|
422 |
- print "rrdtool graph failed: %s" % (exError.output) |
|
441 |
+ except subprocess.CalledProcessError as exError: |
|
442 |
+ print("rrdtool graph failed: %s" % (exError.output)) |
|
423 | 443 |
return False |
424 | 444 |
|
425 |
- if debugOption: |
|
426 |
- print "rrdtool graph: %s" % result, |
|
445 |
+ if verboseMode: |
|
446 |
+ print("rrdtool graph: %s" % result.decode('utf-8'), end='') |
|
427 | 447 |
return True |
428 | 448 |
|
429 | 449 |
##end def |
... | ... |
@@ -435,14 +455,17 @@ def generateGraphs(): |
435 | 455 |
""" |
436 | 456 |
autoScale = False |
437 | 457 |
|
458 |
+ # past 24 hours |
|
438 | 459 |
createGraph('24hr_cpm', 'CPM', 'counts\ per\ minute', |
439 | 460 |
'CPM\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
440 | 461 |
createGraph('24hr_svperhr', 'SvperHr', 'Sv\ per\ hour', |
441 | 462 |
'Sv/Hr\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
463 |
+ # past 4 weeks |
|
442 | 464 |
createGraph('4wk_cpm', 'CPM', 'counts\ per\ minute', |
443 | 465 |
'CPM\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
444 | 466 |
createGraph('4wk_svperhr', 'SvperHr', 'Sv\ per\ hour', |
445 | 467 |
'Sv/Hr\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
468 |
+ # past year |
|
446 | 469 |
createGraph('12m_cpm', 'CPM', 'counts\ per\ minute', |
447 | 470 |
'CPM\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
448 | 471 |
createGraph('12m_svperhr', 'SvperHr', 'Sv\ per\ hour', |
... | ... |
@@ -452,34 +475,32 @@ def generateGraphs(): |
452 | 475 |
def getCLarguments(): |
453 | 476 |
"""Get command line arguments. There are four possible arguments |
454 | 477 |
-d turns on debug mode |
455 |
- -v turns on verbose debug mode |
|
478 |
+ -v turns on verbose mode |
|
456 | 479 |
-t sets the radiation device query interval |
457 | 480 |
-u sets the url of the radiation monitoring device |
458 | 481 |
Returns: nothing |
459 | 482 |
""" |
460 |
- global debugOption, verboseDebug, dataRequestInterval, \ |
|
483 |
+ global verboseMode, debugMode, dataRequestInterval, \ |
|
461 | 484 |
radiationMonitorUrl |
462 | 485 |
|
463 | 486 |
index = 1 |
464 | 487 |
while index < len(sys.argv): |
465 |
- if sys.argv[index] == '-d': |
|
466 |
- debugOption = True |
|
467 |
- elif sys.argv[index] == '-v': |
|
468 |
- debugOption = True |
|
469 |
- verboseDebug = True |
|
488 |
+ if sys.argv[index] == '-v': |
|
489 |
+ verboseMode = True |
|
490 |
+ elif sys.argv[index] == '-d': |
|
491 |
+ verboseMode = True |
|
492 |
+ debugMode = True |
|
470 | 493 |
elif sys.argv[index] == '-t': |
471 |
- try: |
|
472 |
- dataRequestInterval = abs(int(sys.argv[index + 1])) |
|
473 |
- except: |
|
474 |
- print "invalid polling period" |
|
475 |
- exit(-1) |
|
494 |
+ dataRequestInterval = abs(int(sys.argv[index + 1])) |
|
476 | 495 |
index += 1 |
477 | 496 |
elif sys.argv[index] == '-u': |
478 | 497 |
radiationMonitorUrl = sys.argv[index + 1] |
498 |
+ if radiationMonitorUrl.find('http://') < 0: |
|
499 |
+ radiationMonitorUrl = 'http://' + radiationMonitorUrl |
|
479 | 500 |
index += 1 |
480 | 501 |
else: |
481 | 502 |
cmd_name = sys.argv[0].split('/') |
482 |
- print "Usage: %s [-d] [-t seconds] [-u url}" % cmd_name[-1] |
|
503 |
+ print("Usage: %s [-d] [-t seconds] [-u url}" % cmd_name[-1]) |
|
483 | 504 |
exit(-1) |
484 | 505 |
index += 1 |
485 | 506 |
##end def |
... | ... |
@@ -491,9 +512,10 @@ def main(): |
491 | 512 |
Returns: nothing |
492 | 513 |
""" |
493 | 514 |
signal.signal(signal.SIGTERM, terminateAgentProcess) |
515 |
+ signal.signal(signal.SIGINT, terminateAgentProcess) |
|
494 | 516 |
|
495 |
- print '%s starting up radmon agent process' % \ |
|
496 |
- (getTimeStamp()) |
|
517 |
+ print('%s starting up radmon agent process' % \ |
|
518 |
+ (getTimeStamp())) |
|
497 | 519 |
|
498 | 520 |
# last time output JSON file updated |
499 | 521 |
lastDataRequestTime = -1 |
... | ... |
@@ -507,9 +529,9 @@ def main(): |
507 | 529 |
|
508 | 530 |
## Exit with error if rrdtool database does not exist. |
509 | 531 |
if not os.path.exists(_RRD_FILE): |
510 |
- print 'rrdtool database does not exist\n' \ |
|
532 |
+ print('rrdtool database does not exist\n' \ |
|
511 | 533 |
'use createRadmonRrd script to ' \ |
512 |
- 'create rrdtool database\n' |
|
534 |
+ 'create rrdtool database\n') |
|
513 | 535 |
exit(1) |
514 | 536 |
|
515 | 537 |
## main loop |
... | ... |
@@ -517,21 +539,18 @@ def main(): |
517 | 539 |
|
518 | 540 |
currentTime = time.time() # get current time in seconds |
519 | 541 |
|
520 |
- # Every web update interval request data from the radiation |
|
542 |
+ # Every data update interval request data from the radiation |
|
521 | 543 |
# monitor and process the received data. |
522 | 544 |
if currentTime - lastDataRequestTime > dataRequestInterval: |
523 | 545 |
lastDataRequestTime = currentTime |
524 | 546 |
dData = {} |
525 |
- result = True |
|
526 | 547 |
|
527 | 548 |
# Get the data string from the device. |
528 |
- sData = getRadiationData() |
|
529 |
- if sData == None: |
|
530 |
- result = False |
|
549 |
+ result = getRadiationData(dData) |
|
531 | 550 |
|
532 | 551 |
# If successful parse the data. |
533 | 552 |
if result: |
534 |
- result = parseDataString(sData, dData) |
|
553 |
+ result = parseDataString(dData) |
|
535 | 554 |
|
536 | 555 |
# If parsing successful, convert the data. |
537 | 556 |
if result: |
... | ... |
@@ -539,18 +558,18 @@ def main(): |
539 | 558 |
|
540 | 559 |
# If conversion successful, write data to data files. |
541 | 560 |
if result: |
542 |
- writeOutputDataFile(dData) |
|
561 |
+ writeOutputFile(dData) |
|
543 | 562 |
|
544 |
- # At the rrdtool database update interval, update the database. |
|
545 |
- if currentTime - lastDatabaseUpdateTime > \ |
|
546 |
- _DATABASE_UPDATE_INTERVAL: |
|
547 |
- lastDatabaseUpdateTime = currentTime |
|
548 |
- ## Update the round robin database with the parsed data. |
|
549 |
- updateDatabase(dData) |
|
563 |
+ # At the rrdtool database update interval, update the database. |
|
564 |
+ if result and (currentTime - lastDatabaseUpdateTime > \ |
|
565 |
+ _DATABASE_UPDATE_INTERVAL): |
|
566 |
+ lastDatabaseUpdateTime = currentTime |
|
567 |
+ ## Update the round robin database with the parsed data. |
|
568 |
+ result = updateDatabase(dData) |
|
550 | 569 |
|
551 |
- # Set the station status to online or offline depending on the |
|
570 |
+ # Set the radmon status to online or offline depending on the |
|
552 | 571 |
# success or failure of the above operations. |
553 |
- setStationStatus(result) |
|
572 |
+ setRadmonStatus(result) |
|
554 | 573 |
|
555 | 574 |
|
556 | 575 |
# At the chart generation interval, generate charts. |
... | ... |
@@ -563,10 +582,13 @@ def main(): |
563 | 582 |
# the next update interval. |
564 | 583 |
|
565 | 584 |
elapsedTime = time.time() - currentTime |
566 |
- if debugOption and not verboseDebug: |
|
567 |
- pass #print |
|
568 |
- if verboseDebug: |
|
569 |
- print "processing time: %6f sec\n" % elapsedTime |
|
585 |
+ if verboseMode: |
|
586 |
+ if result: |
|
587 |
+ print("update successful: %6f sec\n" |
|
588 |
+ % elapsedTime) |
|
589 |
+ else: |
|
590 |
+ print("update failed: %6f sec\n" |
|
591 |
+ % elapsedTime) |
|
570 | 592 |
remainingTime = dataRequestInterval - elapsedTime |
571 | 593 |
if remainingTime > 0.0: |
572 | 594 |
time.sleep(remainingTime) |
... | ... |
@@ -575,8 +597,5 @@ def main(): |
575 | 597 |
## end def |
576 | 598 |
|
577 | 599 |
if __name__ == '__main__': |
578 |
- try: |
|
579 |
- main() |
|
580 |
- except KeyboardInterrupt: |
|
581 |
- print '\n', |
|
582 |
- terminateAgentProcess('KeyboardInterrupt','Module') |
|
600 |
+ main() |
|
601 |
+ |
... | ... |
@@ -51,7 +51,7 @@ _USER = os.environ['USER'] |
51 | 51 |
### DEFAULT RADIATION MONITOR URL ### |
52 | 52 |
|
53 | 53 |
# ip address of radiation monitoring device |
54 |
-_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
54 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
55 | 55 |
|
56 | 56 |
### FILE AND FOLDER LOCATIONS ### |
57 | 57 |
|
... | ... |
@@ -11,7 +11,7 @@ |
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 |
-# - write the processed weather data to a JSON file for use by html |
|
14 |
+# - write the processed radmon data to a JSON file for use by html |
|
15 | 15 |
# documents |
16 | 16 |
# |
17 | 17 |
# Copyright 2015 Jeff Owrey |
... | ... |
@@ -35,6 +35,7 @@ |
35 | 35 |
# improved radmon device offline status handling |
36 | 36 |
# * v23 released 16 Nov 2018 by J L Owrey: improved fault handling |
37 | 37 |
# and data conversion |
38 |
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
38 | 39 |
|
39 | 40 |
import os |
40 | 41 |
import urllib2 |
... | ... |
@@ -50,7 +51,7 @@ _USER = os.environ['USER'] |
50 | 51 |
### DEFAULT RADIATION MONITOR URL ### |
51 | 52 |
|
52 | 53 |
# ip address of radiation monitoring device |
53 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
54 |
+_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
54 | 55 |
|
55 | 56 |
### FILE AND FOLDER LOCATIONS ### |
56 | 57 |
|
... | ... |
@@ -58,11 +59,9 @@ _DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
58 | 59 |
_DOCROOT_PATH = "/home/%s/public_html/radmon/" % _USER |
59 | 60 |
# folder for charts and output data file |
60 | 61 |
_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
61 |
-# location of data input file |
|
62 |
-_INPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonInputData.dat" |
|
63 | 62 |
# location of data output file |
64 | 63 |
_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
65 |
-# database that stores weather data |
|
64 |
+# database that stores radmon data |
|
66 | 65 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
67 | 66 |
|
68 | 67 |
### GLOBAL CONSTANTS ### |
... | ... |
@@ -81,6 +80,8 @@ _HTTP_REQUEST_TIMEOUT = 3 |
81 | 80 |
_CHART_WIDTH = 600 |
82 | 81 |
# standard chart height in pixels |
83 | 82 |
_CHART_HEIGHT = 150 |
83 |
+# source of time stamp attached to output data file |
|
84 |
+_USE_RADMON_TIMESTAMP = True |
|
84 | 85 |
|
85 | 86 |
### GLOBAL VARIABLES ### |
86 | 87 |
|
... | ... |
@@ -123,13 +124,9 @@ def setStatusToOffline(): |
123 | 124 |
""" |
124 | 125 |
global stationOnline |
125 | 126 |
|
126 |
- # Inform downstream clients by removing input and output |
|
127 |
- # data files. |
|
128 |
- if os.path.exists(_INPUT_DATA_FILE): |
|
129 |
- os.remove(_INPUT_DATA_FILE) |
|
127 |
+ # Inform downstream clients by removing output data file. |
|
130 | 128 |
if os.path.exists(_OUTPUT_DATA_FILE): |
131 | 129 |
os.remove(_OUTPUT_DATA_FILE) |
132 |
- |
|
133 | 130 |
# If the radiation monitor was previously online, then send |
134 | 131 |
# a message that we are now offline. |
135 | 132 |
if stationOnline: |
... | ... |
@@ -145,15 +142,11 @@ def terminateAgentProcess(signal, frame): |
145 | 142 |
signal, frame - dummy parameters |
146 | 143 |
Returns: nothing |
147 | 144 |
""" |
145 |
+ # Inform downstream clients by removing output data file. |
|
146 |
+ if os.path.exists(_OUTPUT_DATA_FILE): |
|
147 |
+ os.remove(_OUTPUT_DATA_FILE) |
|
148 | 148 |
print '%s terminating radmon agent process' % \ |
149 | 149 |
(getTimeStamp()) |
150 |
- |
|
151 |
- # Inform downstream clients by removing input and output |
|
152 |
- # data files. |
|
153 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
154 |
- os.remove(_OUTPUT_DATA_FILE) |
|
155 |
- if os.path.exists(_INPUT_DATA_FILE): |
|
156 |
- os.remove(_INPUT_DATA_FILE) |
|
157 | 150 |
sys.exit(0) |
158 | 151 |
##end def |
159 | 152 |
|
... | ... |
@@ -232,18 +225,19 @@ def convertData(dData): |
232 | 225 |
Returns: True if successful, False otherwise |
233 | 226 |
""" |
234 | 227 |
try: |
235 |
- # Convert the UTC timestamp provided by the radiation monitoring |
|
236 |
- # device to epoch local time in seconds. |
|
237 |
- ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y") |
|
238 |
- epoch_local_sec = calendar.timegm(ts_utc) |
|
239 |
- dData['ELT'] = epoch_local_sec |
|
240 |
- |
|
241 |
- # Uncomment the code line below to use a timestamp generated by the |
|
242 |
- # requesting server (this) instead of the timestamp provided by the |
|
243 |
- # radiation monitoring device. Using the server generated timestamp |
|
244 |
- # prevents errors that occur when the radiation monitoring device |
|
245 |
- # fails to synchronize with a valid NTP time server. |
|
246 |
- #dData['ELT'] = time.time() |
|
228 |
+ if _USE_RADMON_TIMESTAMP: |
|
229 |
+ # Convert the UTC timestamp provided by the radiation monitoring |
|
230 |
+ # device to epoch local time in seconds. |
|
231 |
+ ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y") |
|
232 |
+ epoch_local_sec = calendar.timegm(ts_utc) |
|
233 |
+ dData['ELT'] = epoch_local_sec |
|
234 |
+ else: |
|
235 |
+ # Use a timestamp generated by the requesting server (this) |
|
236 |
+ # instead of the timestamp provided by the radiation monitoring |
|
237 |
+ # device. Using the server generated timestamp prevents errors |
|
238 |
+ # that occur when the radiation monitoring device fails to |
|
239 |
+ # synchronize with a valid NTP time server. |
|
240 |
+ dData['ELT'] = time.time() |
|
247 | 241 |
|
248 | 242 |
dData['Mode'] = dData['Mode'].lower() |
249 | 243 |
dData['uSvPerHr'] = '%.2f' % float(dData.pop('uSv/hr')) |
... | ... |
@@ -264,22 +258,22 @@ def writeOutputDataFile(dData): |
264 | 258 |
to the output data file |
265 | 259 |
Returns: True if successful, False otherwise |
266 | 260 |
""" |
261 |
+ # Create temporary copy of output data items. |
|
262 |
+ dTemp = dict(dData) |
|
267 | 263 |
# Set date to current time and data |
268 |
- dData['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
|
269 |
- |
|
264 |
+ dTemp['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
|
270 | 265 |
# Remove unnecessary data items. |
271 |
- dTemp = dict(dData) |
|
272 | 266 |
dTemp.pop('ELT') |
273 | 267 |
dTemp.pop('UTC') |
274 |
- |
|
275 |
- # Format the weather data as string using java script object notation. |
|
268 |
+ |
|
269 |
+ # Format the radmon data as string using java script object notation. |
|
276 | 270 |
sData = '[{' |
277 | 271 |
for key in dTemp: |
278 |
- sData += '\"%s\":\"%s\",' % (key, dData[key]) |
|
272 |
+ sData += '\"%s\":\"%s\",' % (key, dTemp[key]) |
|
279 | 273 |
sData = sData[:-1] + '}]\n' |
280 | 274 |
|
281 | 275 |
if verboseDebug: |
282 |
- print sData |
|
276 |
+ print sData, |
|
283 | 277 |
|
284 | 278 |
# Write the string to the output data file for use by html documents. |
285 | 279 |
try: |
... | ... |
@@ -293,26 +287,6 @@ def writeOutputDataFile(dData): |
293 | 287 |
return True |
294 | 288 |
## end def |
295 | 289 |
|
296 |
-def writeInputDataFile(sData): |
|
297 |
- """Write raw data from radiation monitor to the input data file. |
|
298 |
- This file may then be accessed by downstream mirror servers. |
|
299 |
- Parameters: |
|
300 |
- sData - a string object containing the raw data from |
|
301 |
- the radiation monitor |
|
302 |
- Returns: True if successful, False otherwise |
|
303 |
- """ |
|
304 |
- sData += "\n" |
|
305 |
- try: |
|
306 |
- fc = open(_INPUT_DATA_FILE, "w") |
|
307 |
- fc.write(sData) |
|
308 |
- fc.close() |
|
309 |
- except Exception, exError: |
|
310 |
- print "%s writeInputDataFile: %s" % (getTimeStamp(), exError) |
|
311 |
- return False |
|
312 |
- |
|
313 |
- return True |
|
314 |
-##end def |
|
315 |
- |
|
316 | 290 |
def setStationStatus(updateSuccess): |
317 | 291 |
"""Detect if radiation monitor is offline or not available on |
318 | 292 |
the network. After a set number of attempts to get data |
... | ... |
@@ -332,13 +306,13 @@ def setStationStatus(updateSuccess): |
332 | 306 |
print '%s radiation monitor online' % getTimeStamp() |
333 | 307 |
stationOnline = True |
334 | 308 |
if debugOption: |
335 |
- print 'radiation update successful' |
|
309 |
+ print 'data request successful' |
|
336 | 310 |
else: |
337 | 311 |
# The last attempt failed, so update the failed attempts |
338 | 312 |
# count. |
339 | 313 |
failedUpdateCount += 1 |
340 | 314 |
if debugOption: |
341 |
- print 'radiation update failed' |
|
315 |
+ print 'data request failed' |
|
342 | 316 |
|
343 | 317 |
if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
344 | 318 |
# Max number of failed data requests, so set |
... | ... |
@@ -346,7 +320,6 @@ def setStationStatus(updateSuccess): |
346 | 320 |
setStatusToOffline() |
347 | 321 |
##end def |
348 | 322 |
|
349 |
- |
|
350 | 323 |
def updateDatabase(dData): |
351 | 324 |
""" |
352 | 325 |
Update the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -386,7 +359,7 @@ def updateDatabase(dData): |
386 | 359 |
|
387 | 360 |
def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
388 | 361 |
lower, upper, addTrend, autoScale): |
389 |
- """Uses rrdtool to create a graph of specified weather data item. |
|
362 |
+ """Uses rrdtool to create a graph of specified radmon data item. |
|
390 | 363 |
Parameters: |
391 | 364 |
fileName - name of file containing the graph |
392 | 365 |
dataItem - data item to be graphed |
... | ... |
@@ -438,7 +411,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
438 | 411 |
% trendWindow[gStart] |
439 | 412 |
|
440 | 413 |
if verboseDebug: |
441 |
- print "%s\n" % strCmd # DEBUG |
|
414 |
+ print "\n%s" % strCmd # DEBUG |
|
442 | 415 |
|
443 | 416 |
# Run the formatted rrdtool command as a subprocess. |
444 | 417 |
try: |
... | ... |
@@ -450,7 +423,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
450 | 423 |
return False |
451 | 424 |
|
452 | 425 |
if debugOption: |
453 |
- print "rrdtool graph: %s" % result |
|
426 |
+ print "rrdtool graph: %s" % result, |
|
454 | 427 |
return True |
455 | 428 |
|
456 | 429 |
##end def |
... | ... |
@@ -535,7 +508,7 @@ def main(): |
535 | 508 |
## Exit with error if rrdtool database does not exist. |
536 | 509 |
if not os.path.exists(_RRD_FILE): |
537 | 510 |
print 'rrdtool database does not exist\n' \ |
538 |
- 'use createWeatherRrd script to ' \ |
|
511 |
+ 'use createRadmonRrd script to ' \ |
|
539 | 512 |
'create rrdtool database\n' |
540 | 513 |
exit(1) |
541 | 514 |
|
... | ... |
@@ -566,7 +539,6 @@ def main(): |
566 | 539 |
|
567 | 540 |
# If conversion successful, write data to data files. |
568 | 541 |
if result: |
569 |
- writeInputDataFile(sData) |
|
570 | 542 |
writeOutputDataFile(dData) |
571 | 543 |
|
572 | 544 |
# At the rrdtool database update interval, update the database. |
... | ... |
@@ -592,7 +564,7 @@ def main(): |
592 | 564 |
|
593 | 565 |
elapsedTime = time.time() - currentTime |
594 | 566 |
if debugOption and not verboseDebug: |
595 |
|
|
567 |
+ pass #print |
|
596 | 568 |
if verboseDebug: |
597 | 569 |
print "processing time: %6f sec\n" % elapsedTime |
598 | 570 |
remainingTime = dataRequestInterval - elapsedTime |
... | ... |
@@ -36,8 +36,6 @@ |
36 | 36 |
# * v23 released 16 Nov 2018 by J L Owrey: improved fault handling |
37 | 37 |
# and data conversion |
38 | 38 |
|
39 |
-_MIRROR_SERVER = False |
|
40 |
- |
|
41 | 39 |
import os |
42 | 40 |
import urllib2 |
43 | 41 |
import sys |
... | ... |
@@ -53,9 +51,6 @@ _USER = os.environ['USER'] |
53 | 51 |
|
54 | 52 |
# ip address of radiation monitoring device |
55 | 53 |
_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
56 |
-# url if this is a mirror server |
|
57 |
-_PRIMARY_SERVER_URL = "{your primary server url}" \ |
|
58 |
- "/radmon/dynamic/radmonInputData.dat" |
|
59 | 54 |
|
60 | 55 |
### FILE AND FOLDER LOCATIONS ### |
61 | 56 |
|
... | ... |
@@ -174,14 +169,12 @@ def getRadiationData(): |
174 | 169 |
""" |
175 | 170 |
global remoteDeviceReset |
176 | 171 |
|
177 |
- if _MIRROR_SERVER: |
|
178 |
- sUrl = _PRIMARY_SERVER_URL |
|
172 |
+ sUrl = radiationMonitorUrl |
|
173 |
+ |
|
174 |
+ if remoteDeviceReset: |
|
175 |
+ sUrl += "/reset" # reboot the radiation monitor |
|
179 | 176 |
else: |
180 |
- sUrl = radiationMonitorUrl |
|
181 |
- if remoteDeviceReset: |
|
182 |
- sUrl += "/reset" # reboot the radiation monitor |
|
183 |
- else: |
|
184 |
- sUrl += "/rdata" # request data from the monitor |
|
177 |
+ sUrl += "/rdata" # request data from the monitor |
|
185 | 178 |
|
186 | 179 |
try: |
187 | 180 |
conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
... | ... |
@@ -285,6 +278,9 @@ def writeOutputDataFile(dData): |
285 | 278 |
sData += '\"%s\":\"%s\",' % (key, dData[key]) |
286 | 279 |
sData = sData[:-1] + '}]\n' |
287 | 280 |
|
281 |
+ if verboseDebug: |
|
282 |
+ print sData |
|
283 |
+ |
|
288 | 284 |
# Write the string to the output data file for use by html documents. |
289 | 285 |
try: |
290 | 286 |
fc = open(_OUTPUT_DATA_FILE, "w") |
... | ... |
@@ -5,13 +5,12 @@ |
5 | 5 |
# Module: radmonAgent.py |
6 | 6 |
# |
7 | 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 |
|
8 |
+# device and Internet web services. The agent periodically sends an http |
|
9 | 9 |
# request to the radiation monitoring device and processes the response from |
10 | 10 |
# the device and performs a number of operations: |
11 |
-# - conversion of data itemsq |
|
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 |
-# - forward the radiation data to other services |
|
15 | 14 |
# - write the processed weather data to a JSON file for use by html |
16 | 15 |
# documents |
17 | 16 |
# |
... | ... |
@@ -56,7 +55,7 @@ _USER = os.environ['USER'] |
56 | 55 |
_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
57 | 56 |
# url if this is a mirror server |
58 | 57 |
_PRIMARY_SERVER_URL = "{your primary server url}" \ |
59 |
- "/{user}/radmon/dynamic/radmonInputData.dat" |
|
58 |
+ "/radmon/dynamic/radmonInputData.dat" |
|
60 | 59 |
|
61 | 60 |
### FILE AND FOLDER LOCATIONS ### |
62 | 61 |
|
... | ... |
@@ -72,6 +71,7 @@ _OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
72 | 71 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
73 | 72 |
|
74 | 73 |
### GLOBAL CONSTANTS ### |
74 |
+ |
|
75 | 75 |
# max number of failed data requests allowed |
76 | 76 |
_MAX_FAILED_DATA_REQUESTS = 2 |
77 | 77 |
# interval in seconds between data requests to radiation monitor |
... | ... |
@@ -91,10 +91,16 @@ _CHART_HEIGHT = 150 |
91 | 91 |
|
92 | 92 |
# turn on or off of verbose debugging information |
93 | 93 |
debugOption = False |
94 |
-# used for detecting system faults and radiation monitor |
|
95 |
-# online or offline status |
|
94 |
+verboseDebug = False |
|
95 |
+ |
|
96 |
+# The following two items are used for detecting system faults |
|
97 |
+# and radiation monitor online or offline status. |
|
98 |
+ |
|
99 |
+# count of failed attempts to get data from radiation monitor |
|
96 | 100 |
failedUpdateCount = 0 |
101 |
+# detected status of radiation monitor device |
|
97 | 102 |
stationOnline = True |
103 |
+ |
|
98 | 104 |
# status of reset command to radiation monitor |
99 | 105 |
remoteDeviceReset = False |
100 | 106 |
# ip address of radiation monitor |
... | ... |
@@ -106,41 +112,49 @@ dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL |
106 | 112 |
|
107 | 113 |
def getTimeStamp(): |
108 | 114 |
""" |
109 |
- Sets the error message time stamp to the local system time. |
|
115 |
+ Set the error message time stamp to the local system time. |
|
110 | 116 |
Parameters: none |
111 |
- Returns string containing the time stamp. |
|
117 |
+ Returns: string containing the time stamp |
|
112 | 118 |
""" |
113 | 119 |
return time.strftime( "%m/%d/%Y %T", time.localtime() ) |
114 | 120 |
##end def |
115 | 121 |
|
116 | 122 |
def setStatusToOffline(): |
117 |
- """Set the status of the the upstream device to "offline" and sends |
|
118 |
- blank data to the downstream clients. |
|
119 |
- Parameters: |
|
120 |
- dData - dictionary object containing weather data |
|
121 |
- Returns nothing. |
|
123 |
+ """Set the detected status of the radiation monitor to |
|
124 |
+ "offline" and inform downstream clients by removing input |
|
125 |
+ and output data files. |
|
126 |
+ Parameters: none |
|
127 |
+ Returns: nothing |
|
122 | 128 |
""" |
123 | 129 |
global stationOnline |
124 | 130 |
|
131 |
+ # Inform downstream clients by removing input and output |
|
132 |
+ # data files. |
|
125 | 133 |
if os.path.exists(_INPUT_DATA_FILE): |
126 | 134 |
os.remove(_INPUT_DATA_FILE) |
127 | 135 |
if os.path.exists(_OUTPUT_DATA_FILE): |
128 | 136 |
os.remove(_OUTPUT_DATA_FILE) |
129 | 137 |
|
130 |
- # If the radiation monitor was previously online, then send a message |
|
131 |
- # that we are now offline. |
|
138 |
+ # If the radiation monitor was previously online, then send |
|
139 |
+ # a message that we are now offline. |
|
132 | 140 |
if stationOnline: |
133 | 141 |
print '%s radiation monitor offline' % getTimeStamp() |
134 | 142 |
stationOnline = False |
135 | 143 |
##end def |
136 | 144 |
|
137 | 145 |
def terminateAgentProcess(signal, frame): |
138 |
- """Send message to log when process killed |
|
139 |
- Parameters: signal, frame - sigint parameters |
|
146 |
+ """Send a message to log when the agent process gets killed |
|
147 |
+ by the operating system. Inform downstream clients |
|
148 |
+ by removing input and output data files. |
|
149 |
+ Parameters: |
|
150 |
+ signal, frame - dummy parameters |
|
140 | 151 |
Returns: nothing |
141 | 152 |
""" |
142 | 153 |
print '%s terminating radmon agent process' % \ |
143 | 154 |
(getTimeStamp()) |
155 |
+ |
|
156 |
+ # Inform downstream clients by removing input and output |
|
157 |
+ # data files. |
|
144 | 158 |
if os.path.exists(_OUTPUT_DATA_FILE): |
145 | 159 |
os.remove(_OUTPUT_DATA_FILE) |
146 | 160 |
if os.path.exists(_INPUT_DATA_FILE): |
... | ... |
@@ -151,15 +165,12 @@ def terminateAgentProcess(signal, frame): |
151 | 165 |
### PUBLIC METHODS ### |
152 | 166 |
|
153 | 167 |
def getRadiationData(): |
154 |
- """Send http request to radiation monitoring device. The response |
|
155 |
- from the device contains the radiation data. The data is formatted |
|
156 |
- as an html document. |
|
157 |
- Parameters: |
|
158 |
- radiationMonitorUrl - url of radiation monitoring device |
|
159 |
- HttpRequesttimeout - how long to wait for device |
|
160 |
- to respond to http request |
|
161 |
- Returns a string containing the radiation data, or None if |
|
162 |
- not successful. |
|
168 |
+ """Send http request to radiation monitoring device. The |
|
169 |
+ response from the device contains the radiation data as |
|
170 |
+ unformatted ascii text. |
|
171 |
+ Parameters: none |
|
172 |
+ Returns: a string containing the radiation data if successful, |
|
173 |
+ or None if not successful |
|
163 | 174 |
""" |
164 | 175 |
global remoteDeviceReset |
165 | 176 |
|
... | ... |
@@ -168,9 +179,9 @@ def getRadiationData(): |
168 | 179 |
else: |
169 | 180 |
sUrl = radiationMonitorUrl |
170 | 181 |
if remoteDeviceReset: |
171 |
- sUrl += "/reset" |
|
182 |
+ sUrl += "/reset" # reboot the radiation monitor |
|
172 | 183 |
else: |
173 |
- sUrl += "/rdata" |
|
184 |
+ sUrl += "/rdata" # request data from the monitor |
|
174 | 185 |
|
175 | 186 |
try: |
176 | 187 |
conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
... | ... |
@@ -184,7 +195,7 @@ def getRadiationData(): |
184 | 195 |
except Exception, exError: |
185 | 196 |
# If no response is received from the device, then assume that |
186 | 197 |
# the device is down or unavailable over the network. In |
187 |
- # that case set the status of the device to offline. |
|
198 |
+ # that case return None to the calling function. |
|
188 | 199 |
if debugOption: |
189 | 200 |
print "http error: %s" % exError |
190 | 201 |
return None |
... | ... |
@@ -198,7 +209,7 @@ def parseDataString(sData, dData): |
198 | 209 |
Parameters: |
199 | 210 |
sData - the string containing the data to be parsed |
200 | 211 |
dData - a dictionary object to contain the parsed data items |
201 |
- Returns true if successful, false otherwise. |
|
212 |
+ Returns: True if successful, False otherwise |
|
202 | 213 |
""" |
203 | 214 |
try: |
204 | 215 |
sTmp = sData[2:-2] |
... | ... |
@@ -213,6 +224,7 @@ def parseDataString(sData, dData): |
213 | 224 |
dData[item.split('=')[0]] = item.split('=')[1] |
214 | 225 |
dData['status'] = 'online' |
215 | 226 |
|
227 |
+ # Verfy the expected number of data items have been received. |
|
216 | 228 |
if len(dData) != 6: |
217 | 229 |
print "%s parse failed: corrupted data string" % getTimeStamp() |
218 | 230 |
return False; |
... | ... |
@@ -223,12 +235,9 @@ def parseDataString(sData, dData): |
223 | 235 |
def convertData(dData): |
224 | 236 |
"""Convert individual radiation data items as necessary. |
225 | 237 |
Parameters: |
226 |
- lsData - a list object containing the radiation data |
|
227 | 238 |
dData - a dictionary object containing the radiation data |
228 |
- Returns true if successful, false otherwise. |
|
239 |
+ Returns: True if successful, False otherwise |
|
229 | 240 |
""" |
230 |
- result = True |
|
231 |
- |
|
232 | 241 |
try: |
233 | 242 |
# Convert the UTC timestamp provided by the radiation monitoring |
234 | 243 |
# device to epoch local time in seconds. |
... | ... |
@@ -248,22 +257,24 @@ def convertData(dData): |
248 | 257 |
|
249 | 258 |
except Exception, exError: |
250 | 259 |
print "%s data conversion failed: %s" % (getTimeStamp(), exError) |
251 |
- result = False |
|
260 |
+ return False |
|
252 | 261 |
|
253 |
- return result |
|
262 |
+ return True |
|
254 | 263 |
##end def |
255 | 264 |
|
256 | 265 |
def writeOutputDataFile(dData): |
257 |
- """Write radiation data items to a JSON formatted file for use by |
|
258 |
- HTML documents. |
|
266 |
+ """Write radiation data items to the output data file, formatted as |
|
267 |
+ a Javascript file. This file may then be accessed and used by |
|
268 |
+ by downstream clients, for instance, in HTML documents. |
|
259 | 269 |
Parameters: |
260 |
- lsData - a list object containing the data to be written |
|
261 |
- to the JSON file |
|
262 |
- Returns true if successful, false otherwise. |
|
270 |
+ dData - a dictionary object containing the data to be written |
|
271 |
+ to the output data file |
|
272 |
+ Returns: True if successful, False otherwise |
|
263 | 273 |
""" |
264 | 274 |
# Set date to current time and data |
265 | 275 |
dData['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
266 | 276 |
|
277 |
+ # Remove unnecessary data items. |
|
267 | 278 |
dTemp = dict(dData) |
268 | 279 |
dTemp.pop('ELT') |
269 | 280 |
dTemp.pop('UTC') |
... | ... |
@@ -287,12 +298,12 @@ def writeOutputDataFile(dData): |
287 | 298 |
## end def |
288 | 299 |
|
289 | 300 |
def writeInputDataFile(sData): |
290 |
- """Write raw data from radiation monitor to file for use by mirror |
|
291 |
- servers. |
|
301 |
+ """Write raw data from radiation monitor to the input data file. |
|
302 |
+ This file may then be accessed by downstream mirror servers. |
|
292 | 303 |
Parameters: |
293 |
- sData - a string object containing the data string from |
|
304 |
+ sData - a string object containing the raw data from |
|
294 | 305 |
the radiation monitor |
295 |
- Returns true if successful, false otherwise. |
|
306 |
+ Returns: True if successful, False otherwise |
|
296 | 307 |
""" |
297 | 308 |
sData += "\n" |
298 | 309 |
try: |
... | ... |
@@ -307,6 +318,14 @@ def writeInputDataFile(sData): |
307 | 318 |
##end def |
308 | 319 |
|
309 | 320 |
def setStationStatus(updateSuccess): |
321 |
+ """Detect if radiation monitor is offline or not available on |
|
322 |
+ the network. After a set number of attempts to get data |
|
323 |
+ from the monitor set a flag that the station is offline. |
|
324 |
+ Parameters: |
|
325 |
+ updateSuccess - a boolean that is True if data request |
|
326 |
+ successful, False otherwise |
|
327 |
+ Returns: nothing |
|
328 |
+ """ |
|
310 | 329 |
global failedUpdateCount, stationOnline |
311 | 330 |
|
312 | 331 |
if updateSuccess: |
... | ... |
@@ -319,33 +338,37 @@ def setStationStatus(updateSuccess): |
319 | 338 |
if debugOption: |
320 | 339 |
print 'radiation update successful' |
321 | 340 |
else: |
341 |
+ # The last attempt failed, so update the failed attempts |
|
342 |
+ # count. |
|
322 | 343 |
failedUpdateCount += 1 |
323 | 344 |
if debugOption: |
324 | 345 |
print 'radiation update failed' |
325 | 346 |
|
326 | 347 |
if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
348 |
+ # Max number of failed data requests, so set |
|
349 |
+ # monitor status to offline. |
|
327 | 350 |
setStatusToOffline() |
328 | 351 |
##end def |
329 | 352 |
|
330 | 353 |
|
331 | 354 |
def updateDatabase(dData): |
332 | 355 |
""" |
333 |
- Updates the rrdtool database by executing an rrdtool system command. |
|
334 |
- Formats the command using the data extracted from the radiation |
|
356 |
+ Update the rrdtool database by executing an rrdtool system command. |
|
357 |
+ Format the command using the data extracted from the radiation |
|
335 | 358 |
monitor response. |
336 | 359 |
Parameters: dData - dictionary object containing data items to be |
337 | 360 |
written to the rr database file |
338 |
- Returns true if successful, false otherwise. |
|
361 |
+ Returns: True if successful, False otherwise |
|
339 | 362 |
""" |
340 | 363 |
global remoteDeviceReset |
341 | 364 |
|
342 | 365 |
# The RR database stores whole units, so convert uSv to Sv. |
343 | 366 |
SvPerHr = float(dData['uSvPerHr']) * 1.0E-06 |
344 | 367 |
|
345 |
- # Create the rrdtool update command. |
|
368 |
+ # Format the rrdtool update command. |
|
346 | 369 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
347 | 370 |
(_RRD_FILE, dData['ELT'], dData['CPM'], SvPerHr) |
348 |
- if debugOption and False: |
|
371 |
+ if verboseDebug: |
|
349 | 372 |
print "%s" % strCmd # DEBUG |
350 | 373 |
|
351 | 374 |
# Run the command as a subprocess. |
... | ... |
@@ -369,10 +392,11 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
369 | 392 |
lower, upper, addTrend, autoScale): |
370 | 393 |
"""Uses rrdtool to create a graph of specified weather data item. |
371 | 394 |
Parameters: |
372 |
- fileName - name of graph image file |
|
395 |
+ fileName - name of file containing the graph |
|
373 | 396 |
dataItem - data item to be graphed |
374 | 397 |
gLabel - string containing a graph label for the data item |
375 | 398 |
gTitle - string containing a title for the graph |
399 |
+ gStart - beginning time of the graphed data |
|
376 | 400 |
lower - lower bound for graph ordinate #NOT USED |
377 | 401 |
upper - upper bound for graph ordinate #NOT USED |
378 | 402 |
addTrend - 0, show only graph data |
... | ... |
@@ -381,7 +405,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
381 | 405 |
autoScale - if True, then use vertical axis auto scaling |
382 | 406 |
(lower and upper parameters are ignored), otherwise use |
383 | 407 |
lower and upper parameters to set vertical axis scale |
384 |
- Returns true if successful, false otherwise. |
|
408 |
+ Returns: True if successful, False otherwise |
|
385 | 409 |
""" |
386 | 410 |
gPath = _CHARTS_DIRECTORY + fileName + ".png" |
387 | 411 |
trendWindow = { 'end-1day': 7200, |
... | ... |
@@ -417,7 +441,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
417 | 441 |
strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
418 | 442 |
% trendWindow[gStart] |
419 | 443 |
|
420 |
- if debugOption and False: |
|
444 |
+ if verboseDebug: |
|
421 | 445 |
print "%s\n" % strCmd # DEBUG |
422 | 446 |
|
423 | 447 |
# Run the formatted rrdtool command as a subprocess. |
... | ... |
@@ -438,7 +462,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
438 | 462 |
def generateGraphs(): |
439 | 463 |
"""Generate graphs for display in html documents. |
440 | 464 |
Parameters: none |
441 |
- Returns nothing. |
|
465 |
+ Returns: nothing |
|
442 | 466 |
""" |
443 | 467 |
autoScale = False |
444 | 468 |
|
... | ... |
@@ -457,18 +481,23 @@ def generateGraphs(): |
457 | 481 |
##end def |
458 | 482 |
|
459 | 483 |
def getCLarguments(): |
460 |
- """Get command line arguments. There are three possible arguments |
|
484 |
+ """Get command line arguments. There are four possible arguments |
|
461 | 485 |
-d turns on debug mode |
486 |
+ -v turns on verbose debug mode |
|
462 | 487 |
-t sets the radiation device query interval |
463 | 488 |
-u sets the url of the radiation monitoring device |
464 |
- Returns nothing. |
|
489 |
+ Returns: nothing |
|
465 | 490 |
""" |
466 |
- global debugOption, dataRequestInterval, radiationMonitorUrl |
|
491 |
+ global debugOption, verboseDebug, dataRequestInterval, \ |
|
492 |
+ radiationMonitorUrl |
|
467 | 493 |
|
468 | 494 |
index = 1 |
469 | 495 |
while index < len(sys.argv): |
470 | 496 |
if sys.argv[index] == '-d': |
471 | 497 |
debugOption = True |
498 |
+ elif sys.argv[index] == '-v': |
|
499 |
+ debugOption = True |
|
500 |
+ verboseDebug = True |
|
472 | 501 |
elif sys.argv[index] == '-t': |
473 | 502 |
try: |
474 | 503 |
dataRequestInterval = abs(int(sys.argv[index + 1])) |
... | ... |
@@ -490,7 +519,7 @@ def main(): |
490 | 519 |
"""Handles timing of events and acts as executive routine managing |
491 | 520 |
all other functions. |
492 | 521 |
Parameters: none |
493 |
- Returns nothing. |
|
522 |
+ Returns: nothing |
|
494 | 523 |
""" |
495 | 524 |
signal.signal(signal.SIGTERM, terminateAgentProcess) |
496 | 525 |
|
... | ... |
@@ -566,9 +595,10 @@ def main(): |
566 | 595 |
# the next update interval. |
567 | 596 |
|
568 | 597 |
elapsedTime = time.time() - currentTime |
569 |
- if debugOption: |
|
598 |
+ if debugOption and not verboseDebug: |
|
570 | 599 |
|
571 |
- #print "processing time: %6f sec\n" % elapsedTime |
|
600 |
+ if verboseDebug: |
|
601 |
+ print "processing time: %6f sec\n" % elapsedTime |
|
572 | 602 |
remainingTime = dataRequestInterval - elapsedTime |
573 | 603 |
if remainingTime > 0.0: |
574 | 604 |
time.sleep(remainingTime) |
... | ... |
@@ -53,10 +53,10 @@ _USER = os.environ['USER'] |
53 | 53 |
### DEFAULT RADIATION MONITOR URL ### |
54 | 54 |
|
55 | 55 |
# ip address of radiation monitoring device |
56 |
-_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
56 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
57 | 57 |
# url if this is a mirror server |
58 |
-_PRIMARY_SERVER_URL = "http://73.157.139.23:7361" \ |
|
59 |
- "/~pi/radmon/dynamic/radmonInputData.dat" |
|
58 |
+_PRIMARY_SERVER_URL = "{your primary server url}" \ |
|
59 |
+ "/{user}/radmon/dynamic/radmonInputData.dat" |
|
60 | 60 |
|
61 | 61 |
### FILE AND FOLDER LOCATIONS ### |
62 | 62 |
|
... | ... |
@@ -34,10 +34,10 @@ |
34 | 34 |
# * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates |
35 | 35 |
# * v22 released 03 Mar 2018 by J L Owrey; improved code readability; |
36 | 36 |
# improved radmon device offline status handling |
37 |
-# * v23 released 15 Nov 2018 by J L Owrey; improved system fault |
|
38 |
-# handling and radiation monitor offline handling |
|
37 |
+# * v23 released 16 Nov 2018 by J L Owrey: improved fault handling |
|
38 |
+# and data conversion |
|
39 | 39 |
|
40 |
-_MIRROR_SERVER = True |
|
40 |
+_MIRROR_SERVER = False |
|
41 | 41 |
|
42 | 42 |
import os |
43 | 43 |
import urllib2 |
... | ... |
@@ -53,9 +53,10 @@ _USER = os.environ['USER'] |
53 | 53 |
### DEFAULT RADIATION MONITOR URL ### |
54 | 54 |
|
55 | 55 |
# ip address of radiation monitoring device |
56 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
56 |
+_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
57 | 57 |
# url if this is a mirror server |
58 |
-_PRIMARY_SERVER_URL = "{your primary server url}" |
|
58 |
+_PRIMARY_SERVER_URL = "http://73.157.139.23:7361" \ |
|
59 |
+ "/~pi/radmon/dynamic/radmonInputData.dat" |
|
59 | 60 |
|
60 | 61 |
### FILE AND FOLDER LOCATIONS ### |
61 | 62 |
|
... | ... |
@@ -243,15 +244,10 @@ def convertData(dData): |
243 | 244 |
#dData['ELT'] = time.time() |
244 | 245 |
|
245 | 246 |
dData['Mode'] = dData['Mode'].lower() |
246 |
- |
|
247 |
- dData['uSvPerHr'] = float(dData.pop('uSv/hr')) |
|
248 |
- |
|
249 |
- dData['CPM'] = int(dData.pop('CPM')) |
|
250 |
- |
|
251 |
- dData['CPS'] = int(dData.pop('CPS')) |
|
247 |
+ dData['uSvPerHr'] = '%.2f' % float(dData.pop('uSv/hr')) |
|
252 | 248 |
|
253 | 249 |
except Exception, exError: |
254 |
- print "%s convert data failed: %s" % (getTimeStamp(), exError) |
|
250 |
+ print "%s data conversion failed: %s" % (getTimeStamp(), exError) |
|
255 | 251 |
result = False |
256 | 252 |
|
257 | 253 |
return result |
... | ... |
@@ -268,14 +264,14 @@ def writeOutputDataFile(dData): |
268 | 264 |
# Set date to current time and data |
269 | 265 |
dData['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
270 | 266 |
|
267 |
+ dTemp = dict(dData) |
|
268 |
+ dTemp.pop('ELT') |
|
269 |
+ dTemp.pop('UTC') |
|
270 |
+ |
|
271 | 271 |
# Format the weather data as string using java script object notation. |
272 | 272 |
sData = '[{' |
273 |
- sData += "\"date\":\"%s\"," % dData['date'] |
|
274 |
- sData += "\"CPM\":\"%d\"," % dData['CPM'] |
|
275 |
- sData += "\"CPS\":\"%d\"," % dData['CPS'] |
|
276 |
- sData += "\"uSvPerHr\":\"%.2f\"," % dData['uSvPerHr'] |
|
277 |
- sData += "\"Mode\":\"%s\"," % dData['Mode'] |
|
278 |
- sData += "\"status\":\"%s\"," % dData['status'] |
|
273 |
+ for key in dTemp: |
|
274 |
+ sData += '\"%s\":\"%s\",' % (key, dData[key]) |
|
279 | 275 |
sData = sData[:-1] + '}]\n' |
280 | 276 |
|
281 | 277 |
# Write the string to the output data file for use by html documents. |
... | ... |
@@ -304,7 +300,7 @@ def writeInputDataFile(sData): |
304 | 300 |
fc.write(sData) |
305 | 301 |
fc.close() |
306 | 302 |
except Exception, exError: |
307 |
- print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
|
303 |
+ print "%s writeInputDataFile: %s" % (getTimeStamp(), exError) |
|
308 | 304 |
return False |
309 | 305 |
|
310 | 306 |
return True |
... | ... |
@@ -344,7 +340,7 @@ def updateDatabase(dData): |
344 | 340 |
global remoteDeviceReset |
345 | 341 |
|
346 | 342 |
# The RR database stores whole units, so convert uSv to Sv. |
347 |
- SvPerHr = dData['uSvPerHr'] * 1.0E-06 |
|
343 |
+ SvPerHr = float(dData['uSvPerHr']) * 1.0E-06 |
|
348 | 344 |
|
349 | 345 |
# Create the rrdtool update command. |
350 | 346 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
... | ... |
@@ -55,7 +55,7 @@ _USER = os.environ['USER'] |
55 | 55 |
# ip address of radiation monitoring device |
56 | 56 |
_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
57 | 57 |
# url if this is a mirror server |
58 |
-_PRIMARY_SERVER_URL = "{your mirror server url}" |
|
58 |
+_PRIMARY_SERVER_URL = "{your primary server url}" |
|
59 | 59 |
|
60 | 60 |
### FILE AND FOLDER LOCATIONS ### |
61 | 61 |
|
... | ... |
@@ -53,10 +53,9 @@ _USER = os.environ['USER'] |
53 | 53 |
### DEFAULT RADIATION MONITOR URL ### |
54 | 54 |
|
55 | 55 |
# ip address of radiation monitoring device |
56 |
-_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
56 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
57 | 57 |
# url if this is a mirror server |
58 |
-_PRIMARY_SERVER_URL = "http://73.157.139.23:7361" \ |
|
59 |
- "/~pi/radmon/dynamic/radmonInputData.dat" |
|
58 |
+_PRIMARY_SERVER_URL = "{your mirror server url}" |
|
60 | 59 |
|
61 | 60 |
### FILE AND FOLDER LOCATIONS ### |
62 | 61 |
|
... | ... |
@@ -15,7 +15,7 @@ |
15 | 15 |
# - write the processed weather data to a JSON file for use by html |
16 | 16 |
# documents |
17 | 17 |
# |
18 |
-# Copyright 2018 Jeff Owrey |
|
18 |
+# Copyright 2015 Jeff Owrey |
|
19 | 19 |
# This program is free software: you can redistribute it and/or modify |
20 | 20 |
# it under the terms of the GNU General Public License as published by |
21 | 21 |
# the Free Software Foundation, either version 3 of the License, or |
... | ... |
@@ -34,9 +34,10 @@ |
34 | 34 |
# * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates |
35 | 35 |
# * v22 released 03 Mar 2018 by J L Owrey; improved code readability; |
36 | 36 |
# improved radmon device offline status handling |
37 |
-# |
|
37 |
+# * v23 released 15 Nov 2018 by J L Owrey; improved system fault |
|
38 |
+# handling and radiation monitor offline handling |
|
38 | 39 |
|
39 |
-_MIRROR_SERVER = False |
|
40 |
+_MIRROR_SERVER = True |
|
40 | 41 |
|
41 | 42 |
import os |
42 | 43 |
import urllib2 |
... | ... |
@@ -52,9 +53,10 @@ _USER = os.environ['USER'] |
52 | 53 |
### DEFAULT RADIATION MONITOR URL ### |
53 | 54 |
|
54 | 55 |
# ip address of radiation monitoring device |
55 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
56 |
+_DEFAULT_RADIATION_MONITOR_URL = "http://192.168.1.24" |
|
56 | 57 |
# url if this is a mirror server |
57 |
-_PRIMARY_SERVER_URL = "{your primary server url}" |
|
58 |
+_PRIMARY_SERVER_URL = "http://73.157.139.23:7361" \ |
|
59 |
+ "/~pi/radmon/dynamic/radmonInputData.dat" |
|
58 | 60 |
|
59 | 61 |
### FILE AND FOLDER LOCATIONS ### |
60 | 62 |
|
... | ... |
@@ -89,10 +91,10 @@ _CHART_HEIGHT = 150 |
89 | 91 |
|
90 | 92 |
# turn on or off of verbose debugging information |
91 | 93 |
debugOption = False |
92 |
-# online status of radiation monitor |
|
93 |
-radiationMonitorOnline = True |
|
94 |
-# number of unsuccessful http requests |
|
95 |
-failedDataRequestCount = 0 |
|
94 |
+# used for detecting system faults and radiation monitor |
|
95 |
+# online or offline status |
|
96 |
+failedUpdateCount = 0 |
|
97 |
+stationOnline = True |
|
96 | 98 |
# status of reset command to radiation monitor |
97 | 99 |
remoteDeviceReset = False |
98 | 100 |
# ip address of radiation monitor |
... | ... |
@@ -111,33 +113,28 @@ def getTimeStamp(): |
111 | 113 |
return time.strftime( "%m/%d/%Y %T", time.localtime() ) |
112 | 114 |
##end def |
113 | 115 |
|
114 |
-def setOfflineStatus(dData): |
|
116 |
+def setStatusToOffline(): |
|
115 | 117 |
"""Set the status of the the upstream device to "offline" and sends |
116 | 118 |
blank data to the downstream clients. |
117 | 119 |
Parameters: |
118 | 120 |
dData - dictionary object containing weather data |
119 | 121 |
Returns nothing. |
120 | 122 |
""" |
121 |
- global radiationMonitorOnline, failedDataRequestCount |
|
123 |
+ global stationOnline |
|
122 | 124 |
|
123 | 125 |
if os.path.exists(_INPUT_DATA_FILE): |
124 | 126 |
os.remove(_INPUT_DATA_FILE) |
125 |
- |
|
126 |
- if failedDataRequestCount < _MAX_FAILED_DATA_REQUESTS - 1: |
|
127 |
- failedDataRequestCount += 1 |
|
128 |
- return |
|
127 |
+ if os.path.exists(_OUTPUT_DATA_FILE): |
|
128 |
+ os.remove(_OUTPUT_DATA_FILE) |
|
129 | 129 |
|
130 | 130 |
# If the radiation monitor was previously online, then send a message |
131 | 131 |
# that we are now offline. |
132 |
- if radiationMonitorOnline: |
|
133 |
- print "%s: radiation monitor offline" % getTimeStamp() |
|
134 |
- radiationMonitorOnline = False |
|
135 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
136 |
- os.remove(_OUTPUT_DATA_FILE) |
|
137 |
- return |
|
132 |
+ if stationOnline: |
|
133 |
+ print '%s radiation monitor offline' % getTimeStamp() |
|
134 |
+ stationOnline = False |
|
138 | 135 |
##end def |
139 | 136 |
|
140 |
-def signal_term_handler(signal, frame): |
|
137 |
+def terminateAgentProcess(signal, frame): |
|
141 | 138 |
"""Send message to log when process killed |
142 | 139 |
Parameters: signal, frame - sigint parameters |
143 | 140 |
Returns: nothing |
... | ... |
@@ -164,8 +161,7 @@ def getRadiationData(): |
164 | 161 |
Returns a string containing the radiation data, or None if |
165 | 162 |
not successful. |
166 | 163 |
""" |
167 |
- global radiationMonitorOnline, failedDataRequestCount, \ |
|
168 |
- remoteDeviceReset |
|
164 |
+ global remoteDeviceReset |
|
169 | 165 |
|
170 | 166 |
if _MIRROR_SERVER: |
171 | 167 |
sUrl = _PRIMARY_SERVER_URL |
... | ... |
@@ -193,13 +189,6 @@ def getRadiationData(): |
193 | 189 |
print "http error: %s" % exError |
194 | 190 |
return None |
195 | 191 |
|
196 |
- # If the radiation monitor was previously offline, then send a message |
|
197 |
- # that we are now online. |
|
198 |
- if not radiationMonitorOnline: |
|
199 |
- print "%s radiation monitor online" % getTimeStamp() |
|
200 |
- radiationMonitorOnline = True |
|
201 |
- |
|
202 |
- failedDataRequestCount = 0 |
|
203 | 192 |
return content |
204 | 193 |
##end def |
205 | 194 |
|
... | ... |
@@ -322,6 +311,28 @@ def writeInputDataFile(sData): |
322 | 311 |
return True |
323 | 312 |
##end def |
324 | 313 |
|
314 |
+def setStationStatus(updateSuccess): |
|
315 |
+ global failedUpdateCount, stationOnline |
|
316 |
+ |
|
317 |
+ if updateSuccess: |
|
318 |
+ failedUpdateCount = 0 |
|
319 |
+ # Set status and send a message to the log if the station was |
|
320 |
+ # previously offline and is now online. |
|
321 |
+ if not stationOnline: |
|
322 |
+ print '%s radiation monitor online' % getTimeStamp() |
|
323 |
+ stationOnline = True |
|
324 |
+ if debugOption: |
|
325 |
+ print 'radiation update successful' |
|
326 |
+ else: |
|
327 |
+ failedUpdateCount += 1 |
|
328 |
+ if debugOption: |
|
329 |
+ print 'radiation update failed' |
|
330 |
+ |
|
331 |
+ if failedUpdateCount >= _MAX_FAILED_DATA_REQUESTS: |
|
332 |
+ setStatusToOffline() |
|
333 |
+##end def |
|
334 |
+ |
|
335 |
+ |
|
325 | 336 |
def updateDatabase(dData): |
326 | 337 |
""" |
327 | 338 |
Updates the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -486,7 +497,7 @@ def main(): |
486 | 497 |
Parameters: none |
487 | 498 |
Returns nothing. |
488 | 499 |
""" |
489 |
- signal.signal(signal.SIGTERM, signal_term_handler) |
|
500 |
+ signal.signal(signal.SIGTERM, terminateAgentProcess) |
|
490 | 501 |
|
491 | 502 |
print '%s starting up radmon agent process' % \ |
492 | 503 |
(getTimeStamp()) |
... | ... |
@@ -523,7 +534,6 @@ def main(): |
523 | 534 |
# Get the data string from the device. |
524 | 535 |
sData = getRadiationData() |
525 | 536 |
if sData == None: |
526 |
- setOfflineStatus(dData) |
|
527 | 537 |
result = False |
528 | 538 |
|
529 | 539 |
# If successful parse the data. |
... | ... |
@@ -538,15 +548,18 @@ def main(): |
538 | 548 |
if result: |
539 | 549 |
writeInputDataFile(sData) |
540 | 550 |
writeOutputDataFile(dData) |
541 |
- if debugOption: |
|
542 |
- print "http request successful" |
|
543 | 551 |
|
544 | 552 |
# At the rrdtool database update interval, update the database. |
545 | 553 |
if currentTime - lastDatabaseUpdateTime > \ |
546 | 554 |
_DATABASE_UPDATE_INTERVAL: |
547 | 555 |
lastDatabaseUpdateTime = currentTime |
548 | 556 |
## Update the round robin database with the parsed data. |
549 |
- result = updateDatabase(dData) |
|
557 |
+ updateDatabase(dData) |
|
558 |
+ |
|
559 |
+ # Set the station status to online or offline depending on the |
|
560 |
+ # success or failure of the above operations. |
|
561 |
+ setStationStatus(result) |
|
562 |
+ |
|
550 | 563 |
|
551 | 564 |
# At the chart generation interval, generate charts. |
552 | 565 |
if currentTime - lastChartUpdateTime > _CHART_UPDATE_INTERVAL: |
... | ... |
@@ -559,7 +572,8 @@ def main(): |
559 | 572 |
|
560 | 573 |
elapsedTime = time.time() - currentTime |
561 | 574 |
if debugOption: |
562 |
- print "processing time: %6f sec\n" % elapsedTime |
|
575 |
|
|
576 |
+ #print "processing time: %6f sec\n" % elapsedTime |
|
563 | 577 |
remainingTime = dataRequestInterval - elapsedTime |
564 | 578 |
if remainingTime > 0.0: |
565 | 579 |
time.sleep(remainingTime) |
... | ... |
@@ -571,9 +585,5 @@ if __name__ == '__main__': |
571 | 585 |
try: |
572 | 586 |
main() |
573 | 587 |
except KeyboardInterrupt: |
574 |
- print '\n%s terminating radmon agent process' % \ |
|
575 |
- (getTimeStamp()) |
|
576 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
577 |
- os.remove(_OUTPUT_DATA_FILE) |
|
578 |
- if os.path.exists(_INPUT_DATA_FILE): |
|
579 |
- os.remove(_INPUT_DATA_FILE) |
|
588 |
+ print '\n', |
|
589 |
+ terminateAgentProcess('KeyboardInterrupt','Module') |
... | ... |
@@ -15,7 +15,7 @@ |
15 | 15 |
# - write the processed weather data to a JSON file for use by html |
16 | 16 |
# documents |
17 | 17 |
# |
18 |
-# Copyright 2015 Jeff Owrey |
|
18 |
+# Copyright 2018 Jeff Owrey |
|
19 | 19 |
# This program is free software: you can redistribute it and/or modify |
20 | 20 |
# it under the terms of the GNU General Public License as published by |
21 | 21 |
# the Free Software Foundation, either version 3 of the License, or |
... | ... |
@@ -52,9 +52,9 @@ _USER = os.environ['USER'] |
52 | 52 |
### DEFAULT RADIATION MONITOR URL ### |
53 | 53 |
|
54 | 54 |
# ip address of radiation monitoring device |
55 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation device url}" |
|
55 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
56 | 56 |
# url if this is a mirror server |
57 |
-_PRIMARY_SERVER_URL = "{your primary server radiation data url}" |
|
57 |
+_PRIMARY_SERVER_URL = "{your primary server url}" |
|
58 | 58 |
|
59 | 59 |
### FILE AND FOLDER LOCATIONS ### |
60 | 60 |
|
1 | 1 |
old mode 100644 |
2 | 2 |
new mode 100755 |
... | ... |
@@ -40,7 +40,8 @@ _MIRROR_SERVER = False |
40 | 40 |
|
41 | 41 |
import os |
42 | 42 |
import urllib2 |
43 |
-import sys |
|
43 |
+import sys |
|
44 |
+import signal |
|
44 | 45 |
import subprocess |
45 | 46 |
import multiprocessing |
46 | 47 |
import time |
... | ... |
@@ -69,6 +70,8 @@ _OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
69 | 70 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
70 | 71 |
|
71 | 72 |
### GLOBAL CONSTANTS ### |
73 |
+# max number of failed data requests allowed |
|
74 |
+_MAX_FAILED_DATA_REQUESTS = 2 |
|
72 | 75 |
# interval in seconds between data requests to radiation monitor |
73 | 76 |
_DEFAULT_DATA_REQUEST_INTERVAL = 5 |
74 | 77 |
# defines how often the charts get updated in seconds |
... | ... |
@@ -77,10 +80,9 @@ _CHART_UPDATE_INTERVAL = 300 |
77 | 80 |
_DATABASE_UPDATE_INTERVAL = 30 |
78 | 81 |
# number seconds to wait for a response to HTTP request |
79 | 82 |
_HTTP_REQUEST_TIMEOUT = 3 |
80 |
-# max number of failed data requests allowed |
|
81 |
-_MAX_FAILED_DATA_REQUESTS = 2 |
|
82 |
-# radmon chart dimensions |
|
83 |
+# standard chart width in pixels |
|
83 | 84 |
_CHART_WIDTH = 600 |
85 |
+# standard chart height in pixels |
|
84 | 86 |
_CHART_HEIGHT = 150 |
85 | 87 |
|
86 | 88 |
### GLOBAL VARIABLES ### |
... | ... |
@@ -135,6 +137,20 @@ def setOfflineStatus(dData): |
135 | 137 |
return |
136 | 138 |
##end def |
137 | 139 |
|
140 |
+def signal_term_handler(signal, frame): |
|
141 |
+ """Send message to log when process killed |
|
142 |
+ Parameters: signal, frame - sigint parameters |
|
143 |
+ Returns: nothing |
|
144 |
+ """ |
|
145 |
+ print '%s terminating radmon agent process' % \ |
|
146 |
+ (getTimeStamp()) |
|
147 |
+ if os.path.exists(_OUTPUT_DATA_FILE): |
|
148 |
+ os.remove(_OUTPUT_DATA_FILE) |
|
149 |
+ if os.path.exists(_INPUT_DATA_FILE): |
|
150 |
+ os.remove(_INPUT_DATA_FILE) |
|
151 |
+ sys.exit(0) |
|
152 |
+##end def |
|
153 |
+ |
|
138 | 154 |
### PUBLIC METHODS ### |
139 | 155 |
|
140 | 156 |
def getRadiationData(): |
... | ... |
@@ -267,9 +283,9 @@ def writeOutputDataFile(dData): |
267 | 283 |
# Format the weather data as string using java script object notation. |
268 | 284 |
sData = '[{' |
269 | 285 |
sData += "\"date\":\"%s\"," % dData['date'] |
270 |
- sData += "\"CPM\":\"%s\"," % dData['CPM'] |
|
271 |
- sData += "\"CPS\":\"%s\"," % dData['CPS'] |
|
272 |
- sData += "\"uSvPerHr\":\"%s\"," % dData['uSvPerHr'] |
|
286 |
+ sData += "\"CPM\":\"%d\"," % dData['CPM'] |
|
287 |
+ sData += "\"CPS\":\"%d\"," % dData['CPS'] |
|
288 |
+ sData += "\"uSvPerHr\":\"%.2f\"," % dData['uSvPerHr'] |
|
273 | 289 |
sData += "\"Mode\":\"%s\"," % dData['Mode'] |
274 | 290 |
sData += "\"status\":\"%s\"," % dData['status'] |
275 | 291 |
sData = sData[:-1] + '}]\n' |
... | ... |
@@ -470,6 +486,10 @@ def main(): |
470 | 486 |
Parameters: none |
471 | 487 |
Returns nothing. |
472 | 488 |
""" |
489 |
+ signal.signal(signal.SIGTERM, signal_term_handler) |
|
490 |
+ |
|
491 |
+ print '%s starting up radmon agent process' % \ |
|
492 |
+ (getTimeStamp()) |
|
473 | 493 |
|
474 | 494 |
# last time output JSON file updated |
475 | 495 |
lastDataRequestTime = -1 |
... | ... |
@@ -483,8 +503,9 @@ def main(): |
483 | 503 |
|
484 | 504 |
## Exit with error if rrdtool database does not exist. |
485 | 505 |
if not os.path.exists(_RRD_FILE): |
486 |
- print "cannot find rrdtool database\nuse createWeatherRrd script to" \ |
|
487 |
- " create rrdtool database\n" |
|
506 |
+ print 'rrdtool database does not exist\n' \ |
|
507 |
+ 'use createWeatherRrd script to ' \ |
|
508 |
+ 'create rrdtool database\n' |
|
488 | 509 |
exit(1) |
489 | 510 |
|
490 | 511 |
## main loop |
... | ... |
@@ -550,7 +571,8 @@ if __name__ == '__main__': |
550 | 571 |
try: |
551 | 572 |
main() |
552 | 573 |
except KeyboardInterrupt: |
553 |
- print '\nInterrupted' |
|
574 |
+ print '\n%s terminating radmon agent process' % \ |
|
575 |
+ (getTimeStamp()) |
|
554 | 576 |
if os.path.exists(_OUTPUT_DATA_FILE): |
555 | 577 |
os.remove(_OUTPUT_DATA_FILE) |
556 | 578 |
if os.path.exists(_INPUT_DATA_FILE): |
1 | 1 |
old mode 100755 |
2 | 2 |
new mode 100644 |
... | ... |
@@ -1,6 +1,6 @@ |
1 | 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. |
|
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 |
# |
... | ... |
@@ -48,12 +48,12 @@ import calendar |
48 | 48 |
|
49 | 49 |
_USER = os.environ['USER'] |
50 | 50 |
|
51 |
- ### DEFAULT WEATHER STATION URL ### |
|
51 |
+ ### DEFAULT RADIATION MONITOR URL ### |
|
52 | 52 |
|
53 | 53 |
# ip address of radiation monitoring device |
54 |
-_DEFAULT_RADIATION_MONITOR_URL = '{your radmon device url}' |
|
54 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation device url}" |
|
55 | 55 |
# url if this is a mirror server |
56 |
-_PRIMARY_SERVER_URL = '{your primary server weather data url}' |
|
56 |
+_PRIMARY_SERVER_URL = "{your primary server radiation data url}" |
|
57 | 57 |
|
58 | 58 |
### FILE AND FOLDER LOCATIONS ### |
59 | 59 |
|
... | ... |
@@ -61,12 +61,12 @@ _PRIMARY_SERVER_URL = '{your primary server weather data url}' |
61 | 61 |
_DOCROOT_PATH = "/home/%s/public_html/radmon/" % _USER |
62 | 62 |
# folder for charts and output data file |
63 | 63 |
_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
64 |
- # database that stores weather data |
|
65 |
-_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
|
66 | 64 |
# location of data input file |
67 | 65 |
_INPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonInputData.dat" |
68 | 66 |
# location of data output file |
69 | 67 |
_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
68 |
+# database that stores weather data |
|
69 |
+_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
|
70 | 70 |
|
71 | 71 |
### GLOBAL CONSTANTS ### |
72 | 72 |
# interval in seconds between data requests to radiation monitor |
... | ... |
@@ -153,10 +153,12 @@ def getRadiationData(): |
153 | 153 |
|
154 | 154 |
if _MIRROR_SERVER: |
155 | 155 |
sUrl = _PRIMARY_SERVER_URL |
156 |
- elif remoteDeviceReset: |
|
157 |
- sUrl = radiationMonitorUrl + "/reset" |
|
158 | 156 |
else: |
159 |
- sUrl = radiationMonitorUrl + "/rdata" |
|
157 |
+ sUrl = radiationMonitorUrl |
|
158 |
+ if remoteDeviceReset: |
|
159 |
+ sUrl += "/reset" |
|
160 |
+ else: |
|
161 |
+ sUrl += "/rdata" |
|
160 | 162 |
|
161 | 163 |
try: |
162 | 164 |
conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
... | ... |
@@ -207,8 +209,7 @@ def parseDataString(sData, dData): |
207 | 209 |
dData['status'] = 'online' |
208 | 210 |
|
209 | 211 |
if len(dData) != 6: |
210 |
- print "%s parse failed: corrupted data string: %s" % \ |
|
211 |
- (getTimeStamp(), sData) |
|
212 |
+ print "%s parse failed: corrupted data string" % getTimeStamp() |
|
212 | 213 |
return False; |
213 | 214 |
|
214 | 215 |
return True |
... | ... |
@@ -224,19 +225,18 @@ def convertData(dData): |
224 | 225 |
result = True |
225 | 226 |
|
226 | 227 |
try: |
227 |
- |
|
228 |
- # Uncomment below to use timestamp from radiation monitoring device |
|
229 |
- # otherwise the requesting server (this) will generate the |
|
230 |
- # timestamp. Allowing the server to generate the timestamp |
|
231 |
- # prevents timestamp errors due to the radiation monitoring device |
|
232 |
- # failing to synchronize with a NTP time server. |
|
233 |
- |
|
234 |
- #dData['UTC'] = time.time() |
|
235 |
- |
|
236 |
- ## Convert UTC from radiation monitoring device to local time. |
|
228 |
+ # Convert the UTC timestamp provided by the radiation monitoring |
|
229 |
+ # device to epoch local time in seconds. |
|
237 | 230 |
ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y") |
238 |
- local_sec = calendar.timegm(ts_utc) |
|
239 |
- dData['UTC'] = local_sec |
|
231 |
+ epoch_local_sec = calendar.timegm(ts_utc) |
|
232 |
+ dData['ELT'] = epoch_local_sec |
|
233 |
+ |
|
234 |
+ # Uncomment the code line below to use a timestamp generated by the |
|
235 |
+ # requesting server (this) instead of the timestamp provided by the |
|
236 |
+ # radiation monitoring device. Using the server generated timestamp |
|
237 |
+ # prevents errors that occur when the radiation monitoring device |
|
238 |
+ # fails to synchronize with a valid NTP time server. |
|
239 |
+ #dData['ELT'] = time.time() |
|
240 | 240 |
|
241 | 241 |
dData['Mode'] = dData['Mode'].lower() |
242 | 242 |
|
... | ... |
@@ -247,7 +247,7 @@ def convertData(dData): |
247 | 247 |
dData['CPS'] = int(dData.pop('CPS')) |
248 | 248 |
|
249 | 249 |
except Exception, exError: |
250 |
- print "%s convertData: %s" % (getTimeStamp(), exError) |
|
250 |
+ print "%s convert data failed: %s" % (getTimeStamp(), exError) |
|
251 | 251 |
result = False |
252 | 252 |
|
253 | 253 |
return result |
... | ... |
@@ -262,13 +262,17 @@ def writeOutputDataFile(dData): |
262 | 262 |
Returns true if successful, false otherwise. |
263 | 263 |
""" |
264 | 264 |
# Set date to current time and data |
265 |
- dData['date'] = getTimeStamp() |
|
265 |
+ dData['date'] = time.strftime("%m/%d/%Y %T", time.localtime(dData['ELT'])) |
|
266 | 266 |
|
267 | 267 |
# Format the weather data as string using java script object notation. |
268 | 268 |
sData = '[{' |
269 |
- for key in dData: |
|
270 |
- sData += "\"%s\":\"%s\"," % (key, dData[key]) |
|
271 |
- sData = sData[:-1] + '}]' |
|
269 |
+ sData += "\"date\":\"%s\"," % dData['date'] |
|
270 |
+ sData += "\"CPM\":\"%s\"," % dData['CPM'] |
|
271 |
+ sData += "\"CPS\":\"%s\"," % dData['CPS'] |
|
272 |
+ sData += "\"uSvPerHr\":\"%s\"," % dData['uSvPerHr'] |
|
273 |
+ sData += "\"Mode\":\"%s\"," % dData['Mode'] |
|
274 |
+ sData += "\"status\":\"%s\"," % dData['status'] |
|
275 |
+ sData = sData[:-1] + '}]\n' |
|
272 | 276 |
|
273 | 277 |
# Write the string to the output data file for use by html documents. |
274 | 278 |
try: |
... | ... |
@@ -318,7 +322,7 @@ def updateDatabase(dData): |
318 | 322 |
|
319 | 323 |
# Create the rrdtool update command. |
320 | 324 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
321 |
- (_RRD_FILE, dData['UTC'], dData['CPM'], SvPerHr) |
|
325 |
+ (_RRD_FILE, dData['ELT'], dData['CPM'], SvPerHr) |
|
322 | 326 |
if debugOption and False: |
323 | 327 |
print "%s" % strCmd # DEBUG |
324 | 328 |
|
... | ... |
@@ -32,7 +32,8 @@ |
32 | 32 |
# Revision History |
33 | 33 |
# * v20 released 15 Sep 2015 by J L Owrey; first release |
34 | 34 |
# * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates |
35 |
-# * v22 released 03 Mar 2018 by J L Owrey; improved code readability |
|
35 |
+# * v22 released 03 Mar 2018 by J L Owrey; improved code readability; |
|
36 |
+# improved radmon device offline status handling |
|
36 | 37 |
# |
37 | 38 |
|
38 | 39 |
_MIRROR_SERVER = False |
... | ... |
@@ -50,9 +51,9 @@ _USER = os.environ['USER'] |
50 | 51 |
### DEFAULT WEATHER STATION URL ### |
51 | 52 |
|
52 | 53 |
# ip address of radiation monitoring device |
53 |
-_DEFAULT_RADIATION_MONITOR_URL = '{your radiation monitor device url}' |
|
54 |
+_DEFAULT_RADIATION_MONITOR_URL = '{your radmon device url}' |
|
54 | 55 |
# url if this is a mirror server |
55 |
-_PRIMARY_SERVER_URL = '{your primary server radmon data url}' |
|
56 |
+_PRIMARY_SERVER_URL = '{your primary server weather data url}' |
|
56 | 57 |
|
57 | 58 |
### FILE AND FOLDER LOCATIONS ### |
58 | 59 |
|
... | ... |
@@ -129,15 +130,8 @@ def setOfflineStatus(dData): |
129 | 130 |
if radiationMonitorOnline: |
130 | 131 |
print "%s: radiation monitor offline" % getTimeStamp() |
131 | 132 |
radiationMonitorOnline = False |
132 |
- |
|
133 |
- dData['UTC'] = '' |
|
134 |
- dData['Mode'] = '' |
|
135 |
- dData['uSvPerHr'] = '' |
|
136 |
- dData['CPM'] = '' |
|
137 |
- dData['CPS'] = '' |
|
138 |
- dData['status'] = 'offline' |
|
139 |
- |
|
140 |
- writeOutputDataFile(dData) |
|
133 |
+ if os.path.exists(_OUTPUT_DATA_FILE): |
|
134 |
+ os.remove(_OUTPUT_DATA_FILE) |
|
141 | 135 |
return |
142 | 136 |
##end def |
143 | 137 |
|
1 | 1 |
old mode 100644 |
2 | 2 |
new mode 100755 |
... | ... |
@@ -8,7 +8,7 @@ |
8 | 8 |
# device and the Internet web server. The agent periodically sends an http |
9 | 9 |
# request to the radiation monitoring device and processes the response from |
10 | 10 |
# the device and performs a number of operations: |
11 |
-# - conversion of data items |
|
11 |
+# - conversion of data itemsq |
|
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 |
... | ... |
@@ -32,6 +32,7 @@ |
32 | 32 |
# Revision History |
33 | 33 |
# * v20 released 15 Sep 2015 by J L Owrey; first release |
34 | 34 |
# * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates |
35 |
+# * v22 released 03 Mar 2018 by J L Owrey; improved code readability |
|
35 | 36 |
# |
36 | 37 |
|
37 | 38 |
_MIRROR_SERVER = False |
... | ... |
@@ -49,10 +50,9 @@ _USER = os.environ['USER'] |
49 | 50 |
### DEFAULT WEATHER STATION URL ### |
50 | 51 |
|
51 | 52 |
# ip address of radiation monitoring device |
52 |
-_RADIATION_MONITOR_URL = "http://YOUR_RADIATION_MONITOR_IP_ADDRESS" |
|
53 |
+_DEFAULT_RADIATION_MONITOR_URL = '{your radiation monitor device url}' |
|
53 | 54 |
# url if this is a mirror server |
54 |
-_PRIMARY_SERVER_URL = \ |
|
55 |
- "http://YOUR_PRIMARY_SERVER/USER/radmon/dynamic/radmonInputData.dat" |
|
55 |
+_PRIMARY_SERVER_URL = '{your primary server radmon data url}' |
|
56 | 56 |
|
57 | 57 |
### FILE AND FOLDER LOCATIONS ### |
58 | 58 |
|
... | ... |
@@ -63,7 +63,7 @@ _CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
63 | 63 |
# database that stores weather data |
64 | 64 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
65 | 65 |
# location of data input file |
66 |
-INPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonInputData.dat" |
|
66 |
+_INPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonInputData.dat" |
|
67 | 67 |
# location of data output file |
68 | 68 |
_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
69 | 69 |
|
... | ... |
@@ -77,7 +77,7 @@ _DATABASE_UPDATE_INTERVAL = 30 |
77 | 77 |
# number seconds to wait for a response to HTTP request |
78 | 78 |
_HTTP_REQUEST_TIMEOUT = 3 |
79 | 79 |
# max number of failed data requests allowed |
80 |
-_MAX_RADIATION_MONITOR_OFFLINE_COUNT = 2 |
|
80 |
+_MAX_FAILED_DATA_REQUESTS = 2 |
|
81 | 81 |
# radmon chart dimensions |
82 | 82 |
_CHART_WIDTH = 600 |
83 | 83 |
_CHART_HEIGHT = 150 |
... | ... |
@@ -89,11 +89,11 @@ debugOption = False |
89 | 89 |
# online status of radiation monitor |
90 | 90 |
radiationMonitorOnline = True |
91 | 91 |
# number of unsuccessful http requests |
92 |
-radiationMonitorOfflineCount = 0 |
|
92 |
+failedDataRequestCount = 0 |
|
93 | 93 |
# status of reset command to radiation monitor |
94 | 94 |
remoteDeviceReset = False |
95 | 95 |
# ip address of radiation monitor |
96 |
-radiationMonitorUrl = _RADIATION_MONITOR_URL |
|
96 |
+radiationMonitorUrl = _DEFAULT_RADIATION_MONITOR_URL |
|
97 | 97 |
# web update frequency |
98 | 98 |
dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL |
99 | 99 |
|
... | ... |
@@ -115,27 +115,29 @@ def setOfflineStatus(dData): |
115 | 115 |
dData - dictionary object containing weather data |
116 | 116 |
Returns nothing. |
117 | 117 |
""" |
118 |
- global radiationMonitorOnline, radiationMonitorOfflineCount |
|
118 |
+ global radiationMonitorOnline, failedDataRequestCount |
|
119 | 119 |
|
120 |
- radiationMonitorOfflineCount += 1 |
|
120 |
+ if os.path.exists(_INPUT_DATA_FILE): |
|
121 |
+ os.remove(_INPUT_DATA_FILE) |
|
121 | 122 |
|
122 |
- if radiationMonitorOfflineCount < _MAX_RADIATION_MONITOR_OFFLINE_COUNT: |
|
123 |
+ if failedDataRequestCount < _MAX_FAILED_DATA_REQUESTS - 1: |
|
124 |
+ failedDataRequestCount += 1 |
|
123 | 125 |
return |
124 | 126 |
|
125 | 127 |
# If the radiation monitor was previously online, then send a message |
126 | 128 |
# that we are now offline. |
127 | 129 |
if radiationMonitorOnline: |
128 | 130 |
print "%s: radiation monitor offline" % getTimeStamp() |
129 |
- if os.path.exists(INPUT_DATA_FILE): |
|
130 |
- os.remove(INPUT_DATA_FILE) |
|
131 | 131 |
radiationMonitorOnline = False |
132 | 132 |
|
133 |
- for key in dData: |
|
134 |
- dData[key] = '' |
|
135 |
- |
|
136 |
- dData['status'] = 'offline' |
|
133 |
+ dData['UTC'] = '' |
|
134 |
+ dData['Mode'] = '' |
|
135 |
+ dData['uSvPerHr'] = '' |
|
136 |
+ dData['CPM'] = '' |
|
137 |
+ dData['CPS'] = '' |
|
138 |
+ dData['status'] = 'offline' |
|
137 | 139 |
|
138 |
- writeOutputDataFile(dData) |
|
140 |
+ writeOutputDataFile(dData) |
|
139 | 141 |
return |
140 | 142 |
##end def |
141 | 143 |
|
... | ... |
@@ -152,7 +154,7 @@ def getRadiationData(): |
152 | 154 |
Returns a string containing the radiation data, or None if |
153 | 155 |
not successful. |
154 | 156 |
""" |
155 |
- global radiationMonitorOnline, radiationMonitorOfflineCount, \ |
|
157 |
+ global radiationMonitorOnline, failedDataRequestCount, \ |
|
156 | 158 |
remoteDeviceReset |
157 | 159 |
|
158 | 160 |
if _MIRROR_SERVER: |
... | ... |
@@ -179,15 +181,13 @@ def getRadiationData(): |
179 | 181 |
print "http error: %s" % exError |
180 | 182 |
return None |
181 | 183 |
|
182 |
- radiationMonitorOfflineCount = 0 |
|
183 |
- |
|
184 | 184 |
# If the radiation monitor was previously offline, then send a message |
185 | 185 |
# that we are now online. |
186 | 186 |
if not radiationMonitorOnline: |
187 | 187 |
print "%s radiation monitor online" % getTimeStamp() |
188 | 188 |
radiationMonitorOnline = True |
189 | 189 |
|
190 |
- #print content |
|
190 |
+ failedDataRequestCount = 0 |
|
191 | 191 |
return content |
192 | 192 |
##end def |
193 | 193 |
|
... | ... |
@@ -250,6 +250,8 @@ def convertData(dData): |
250 | 250 |
|
251 | 251 |
dData['CPM'] = int(dData.pop('CPM')) |
252 | 252 |
|
253 |
+ dData['CPS'] = int(dData.pop('CPS')) |
|
254 |
+ |
|
253 | 255 |
except Exception, exError: |
254 | 256 |
print "%s convertData: %s" % (getTimeStamp(), exError) |
255 | 257 |
result = False |
... | ... |
@@ -258,7 +260,8 @@ def convertData(dData): |
258 | 260 |
##end def |
259 | 261 |
|
260 | 262 |
def writeOutputDataFile(dData): |
261 |
- """Convert individual weather string data items as necessary. |
|
263 |
+ """Write radiation data items to a JSON formatted file for use by |
|
264 |
+ HTML documents. |
|
262 | 265 |
Parameters: |
263 | 266 |
lsData - a list object containing the data to be written |
264 | 267 |
to the JSON file |
... | ... |
@@ -282,17 +285,20 @@ def writeOutputDataFile(dData): |
282 | 285 |
print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
283 | 286 |
return False |
284 | 287 |
|
285 |
- if debugOption and 0: |
|
286 |
- print sData |
|
287 |
- |
|
288 | 288 |
return True |
289 | 289 |
## end def |
290 | 290 |
|
291 | 291 |
def writeInputDataFile(sData): |
292 |
- # Write the string to the output data file for use by html documents. |
|
292 |
+ """Write raw data from radiation monitor to file for use by mirror |
|
293 |
+ servers. |
|
294 |
+ Parameters: |
|
295 |
+ sData - a string object containing the data string from |
|
296 |
+ the radiation monitor |
|
297 |
+ Returns true if successful, false otherwise. |
|
298 |
+ """ |
|
293 | 299 |
sData += "\n" |
294 | 300 |
try: |
295 |
- fc = open(INPUT_DATA_FILE, "w") |
|
301 |
+ fc = open(_INPUT_DATA_FILE, "w") |
|
296 | 302 |
fc.write(sData) |
297 | 303 |
fc.close() |
298 | 304 |
except Exception, exError: |
... | ... |
@@ -314,13 +320,12 @@ def updateDatabase(dData): |
314 | 320 |
global remoteDeviceReset |
315 | 321 |
|
316 | 322 |
# 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 |
|
323 |
+ SvPerHr = dData['uSvPerHr'] * 1.0E-06 |
|
319 | 324 |
|
320 | 325 |
# Create the rrdtool update command. |
321 | 326 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
322 |
- (_RRD_FILE, dData['UTC'], dData['CPM'], Svvalue) |
|
323 |
- if debugOption: |
|
327 |
+ (_RRD_FILE, dData['UTC'], dData['CPM'], SvPerHr) |
|
328 |
+ if debugOption and False: |
|
324 | 329 |
print "%s" % strCmd # DEBUG |
325 | 330 |
|
326 | 331 |
# Run the command as a subprocess. |
... | ... |
@@ -329,15 +334,15 @@ def updateDatabase(dData): |
329 | 334 |
stderr=subprocess.STDOUT) |
330 | 335 |
except subprocess.CalledProcessError, exError: |
331 | 336 |
print "%s: rrdtool update failed: %s" % \ |
332 |
- (getTimeStamp(), exError.output) |
|
337 |
+ (getTimeStamp(), exError.output) |
|
333 | 338 |
if exError.output.find("illegal attempt to update using time") > -1: |
334 | 339 |
remoteDeviceReset = True |
335 |
- print "%s: rebooting radiation monitor" % \ |
|
336 |
- (getTimeStamp()) |
|
337 |
- |
|
340 |
+ print "%s: rebooting radiation monitor" % (getTimeStamp()) |
|
338 | 341 |
return False |
339 |
- |
|
340 |
- return True |
|
342 |
+ else: |
|
343 |
+ if debugOption: |
|
344 |
+ print 'database update sucessful' |
|
345 |
+ return True |
|
341 | 346 |
##end def |
342 | 347 |
|
343 | 348 |
def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
... | ... |
@@ -392,7 +397,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
392 | 397 |
strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
393 | 398 |
% trendWindow[gStart] |
394 | 399 |
|
395 |
- if debugOption: |
|
400 |
+ if debugOption and False: |
|
396 | 401 |
print "%s\n" % strCmd # DEBUG |
397 | 402 |
|
398 | 403 |
# Run the formatted rrdtool command as a subprocess. |
... | ... |
@@ -475,9 +480,6 @@ def main(): |
475 | 480 |
# last time the rrdtool database updated |
476 | 481 |
lastDatabaseUpdateTime = -1 |
477 | 482 |
|
478 |
- # define empty dictionary object for radmon data |
|
479 |
- dData = {} |
|
480 |
- |
|
481 | 483 |
## Get command line arguments. |
482 | 484 |
getCLarguments() |
483 | 485 |
|
... | ... |
@@ -496,6 +498,7 @@ def main(): |
496 | 498 |
# monitor and process the received data. |
497 | 499 |
if currentTime - lastDataRequestTime > dataRequestInterval: |
498 | 500 |
lastDataRequestTime = currentTime |
501 |
+ dData = {} |
|
499 | 502 |
result = True |
500 | 503 |
|
501 | 504 |
# Get the data string from the device. |
... | ... |
@@ -506,14 +509,13 @@ def main(): |
506 | 509 |
|
507 | 510 |
# If successful parse the data. |
508 | 511 |
if result: |
509 |
- dData = {} |
|
510 | 512 |
result = parseDataString(sData, dData) |
511 | 513 |
|
512 | 514 |
# If parsing successful, convert the data. |
513 | 515 |
if result: |
514 | 516 |
result = convertData(dData) |
515 | 517 |
|
516 |
- # If conversion successful, write data to output file. |
|
518 |
+ # If conversion successful, write data to data files. |
|
517 | 519 |
if result: |
518 | 520 |
writeInputDataFile(sData) |
519 | 521 |
writeOutputDataFile(dData) |
... | ... |
@@ -547,5 +549,11 @@ def main(): |
547 | 549 |
## end def |
548 | 550 |
|
549 | 551 |
if __name__ == '__main__': |
550 |
- main() |
|
551 |
- |
|
552 |
+ try: |
|
553 |
+ main() |
|
554 |
+ except KeyboardInterrupt: |
|
555 |
+ print '\nInterrupted' |
|
556 |
+ if os.path.exists(_OUTPUT_DATA_FILE): |
|
557 |
+ os.remove(_OUTPUT_DATA_FILE) |
|
558 |
+ if os.path.exists(_INPUT_DATA_FILE): |
|
559 |
+ os.remove(_INPUT_DATA_FILE) |
1 | 1 |
old mode 100755 |
2 | 2 |
new mode 100644 |
... | ... |
@@ -34,7 +34,7 @@ |
34 | 34 |
# * v21 released 27 Nov 2017 by J L Owrey; bug fixes; updates |
35 | 35 |
# |
36 | 36 |
|
37 |
-_MIRROR_SERVER = True |
|
37 |
+_MIRROR_SERVER = False |
|
38 | 38 |
|
39 | 39 |
import os |
40 | 40 |
import urllib2 |
... | ... |
@@ -48,24 +48,24 @@ _USER = os.environ['USER'] |
48 | 48 |
|
49 | 49 |
### DEFAULT WEATHER STATION URL ### |
50 | 50 |
|
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" |
|
51 |
+# ip address of radiation monitoring device |
|
52 |
+_RADIATION_MONITOR_URL = "http://YOUR_RADIATION_MONITOR_IP_ADDRESS" |
|
53 |
+# url if this is a mirror server |
|
54 |
+_PRIMARY_SERVER_URL = \ |
|
55 |
+ "http://YOUR_PRIMARY_SERVER/USER/radmon/dynamic/radmonInputData.dat" |
|
56 | 56 |
|
57 | 57 |
### FILE AND FOLDER LOCATIONS ### |
58 | 58 |
|
59 | 59 |
# folder for containing dynamic data objects |
60 |
-_DYNAMIC_FOLDER_PATH = "/home/%s/public_html/radmon/dynamic/" % _USER |
|
60 |
+_DOCROOT_PATH = "/home/%s/public_html/radmon/" % _USER |
|
61 | 61 |
# folder for charts and output data file |
62 |
-_CHARTS_DIRECTORY = _DYNAMIC_FOLDER_PATH |
|
62 |
+_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
|
63 | 63 |
# database that stores weather data |
64 | 64 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER |
65 |
+# location of data input file |
|
66 |
+INPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonInputData.dat" |
|
65 | 67 |
# 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" |
|
68 |
+_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/radmonOutputData.js" |
|
69 | 69 |
|
70 | 70 |
### GLOBAL CONSTANTS ### |
71 | 71 |
# interval in seconds between data requests to radiation monitor |
... | ... |
@@ -92,11 +92,10 @@ radiationMonitorOnline = True |
92 | 92 |
radiationMonitorOfflineCount = 0 |
93 | 93 |
# status of reset command to radiation monitor |
94 | 94 |
remoteDeviceReset = False |
95 |
+# ip address of radiation monitor |
|
96 |
+radiationMonitorUrl = _RADIATION_MONITOR_URL |
|
95 | 97 |
# web update frequency |
96 | 98 |
dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL |
97 |
-# radiation monitor network address |
|
98 |
-radiationMonitorUrl = _DEFAULT_RADIATION_MONITOR_URL |
|
99 |
- |
|
100 | 99 |
|
101 | 100 |
### PRIVATE METHODS ### |
102 | 101 |
|
... | ... |
@@ -127,8 +126,8 @@ def setOfflineStatus(dData): |
127 | 126 |
# that we are now offline. |
128 | 127 |
if radiationMonitorOnline: |
129 | 128 |
print "%s: radiation monitor offline" % getTimeStamp() |
130 |
- if os.path.exists(_DATA_FORWARDING_FILE): |
|
131 |
- os.remove(_DATA_FORWARDING_FILE) |
|
129 |
+ if os.path.exists(INPUT_DATA_FILE): |
|
130 |
+ os.remove(INPUT_DATA_FILE) |
|
132 | 131 |
radiationMonitorOnline = False |
133 | 132 |
|
134 | 133 |
for key in dData: |
... | ... |
@@ -156,14 +155,12 @@ def getRadiationData(): |
156 | 155 |
global radiationMonitorOnline, radiationMonitorOfflineCount, \ |
157 | 156 |
remoteDeviceReset |
158 | 157 |
|
159 |
- sUrl = radiationMonitorUrl |
|
160 |
- |
|
161 |
- if not _MIRROR_SERVER: |
|
162 |
- if remoteDeviceReset: |
|
163 |
- sUrl += "/reset" |
|
164 |
- remoteDeviceReset = False |
|
165 |
- else: |
|
166 |
- sUrl += "/rdata" |
|
158 |
+ if _MIRROR_SERVER: |
|
159 |
+ sUrl = _PRIMARY_SERVER_URL |
|
160 |
+ elif remoteDeviceReset: |
|
161 |
+ sUrl = radiationMonitorUrl + "/reset" |
|
162 |
+ else: |
|
163 |
+ sUrl = radiationMonitorUrl + "/rdata" |
|
167 | 164 |
|
168 | 165 |
try: |
169 | 166 |
conn = urllib2.urlopen(sUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
... | ... |
@@ -190,10 +187,7 @@ def getRadiationData(): |
190 | 187 |
print "%s radiation monitor online" % getTimeStamp() |
191 | 188 |
radiationMonitorOnline = True |
192 | 189 |
|
193 |
- if debugOption: |
|
194 |
- #print content |
|
195 |
- pass |
|
196 |
- |
|
190 |
+ #print content |
|
197 | 191 |
return content |
198 | 192 |
##end def |
199 | 193 |
|
... | ... |
@@ -218,6 +212,11 @@ def parseDataString(sData, dData): |
218 | 212 |
dData[item.split('=')[0]] = item.split('=')[1] |
219 | 213 |
dData['status'] = 'online' |
220 | 214 |
|
215 |
+ if len(dData) != 6: |
|
216 |
+ print "%s parse failed: corrupted data string: %s" % \ |
|
217 |
+ (getTimeStamp(), sData) |
|
218 |
+ return False; |
|
219 |
+ |
|
221 | 220 |
return True |
222 | 221 |
##end def |
223 | 222 |
|
... | ... |
@@ -289,10 +288,11 @@ def writeOutputDataFile(dData): |
289 | 288 |
return True |
290 | 289 |
## end def |
291 | 290 |
|
292 |
-def writeForwardingFile(sData): |
|
291 |
+def writeInputDataFile(sData): |
|
293 | 292 |
# Write the string to the output data file for use by html documents. |
293 |
+ sData += "\n" |
|
294 | 294 |
try: |
295 |
- fc = open(_DATA_FORWARDING_FILE, "w") |
|
295 |
+ fc = open(INPUT_DATA_FILE, "w") |
|
296 | 296 |
fc.write(sData) |
297 | 297 |
fc.close() |
298 | 298 |
except Exception, exError: |
... | ... |
@@ -515,7 +515,7 @@ def main(): |
515 | 515 |
|
516 | 516 |
# If conversion successful, write data to output file. |
517 | 517 |
if result: |
518 |
- writeForwardingFile(sData) |
|
518 |
+ writeInputDataFile(sData) |
|
519 | 519 |
writeOutputDataFile(dData) |
520 | 520 |
if debugOption: |
521 | 521 |
print "http request successful" |
... | ... |
@@ -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) |
... | ... |
@@ -31,18 +31,24 @@ |
31 | 31 |
# Revision History |
32 | 32 |
# * v20 released 15 Sep 2015 by J L Owrey |
33 | 33 |
# |
34 |
+ |
|
35 |
+import os |
|
34 | 36 |
import urllib2 |
35 |
-import time |
|
36 |
-import calendar |
|
37 |
+import sys |
|
37 | 38 |
import subprocess |
38 |
-import sys |
|
39 |
-import os |
|
40 |
-import json |
|
41 | 39 |
import multiprocessing |
40 |
+import time |
|
41 |
+import calendar |
|
42 |
+ |
|
43 |
+_USER = os.environ['USER'] |
|
44 |
+ |
|
45 |
+ ### DEFAULT WEATHER STATION URL ### |
|
46 |
+ |
|
47 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your weather station url}" |
|
48 |
+_DATA_FORWARDING_FILE = "/home/%s/public_html/radmon/dynamic/rad.dat" % _USER |
|
42 | 49 |
|
43 | 50 |
### FILE AND FOLDER LOCATIONS ### |
44 | 51 |
|
45 |
-_USER = os.environ['USER'] |
|
46 | 52 |
_TMP_DIRECTORY = "/tmp/radmon" # folder for charts and output data file |
47 | 53 |
_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER # database that stores the data |
48 | 54 |
_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
... | ... |
@@ -50,13 +56,12 @@ _OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
50 | 56 |
### GLOBAL CONSTANTS ### |
51 | 57 |
|
52 | 58 |
_DEFAULT_DATA_REQUEST_INTERVAL = 10 # interval between data requests to radiation monitor |
53 |
-_CHART_UPDATE_INTERVAL = 300 # defines how often the charts get updated |
|
59 |
+_CHART_UPDATE_INTERVAL = 300 # defines how often the charts get updated in seconds |
|
54 | 60 |
_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated |
55 |
-_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
|
56 |
-_MAX_RADIATION_MONITOR_OFFLINE_COUNT = 3 # max number of failed data requests allowed |
|
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 |
|
57 | 63 |
_CHART_WIDTH = 600 |
58 | 64 |
_CHART_HEIGHT = 150 |
59 |
-_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
60 | 65 |
|
61 | 66 |
### GLOBAL VARIABLES ### |
62 | 67 |
|
... | ... |
@@ -126,7 +131,7 @@ def getRadiationData(): |
126 | 131 |
global radiationMonitorOnline, radiationMonitorOfflineCount |
127 | 132 |
|
128 | 133 |
try: |
129 |
- conn = urllib2.urlopen(radiationMonitorUrl + "/rdata", |
|
134 |
+ conn = urllib2.urlopen(radiationMonitorUrl, |
|
130 | 135 |
timeout=_HTTP_REQUEST_TIMEOUT) |
131 | 136 |
|
132 | 137 |
# Format received data into a single string. |
... | ... |
@@ -152,8 +157,9 @@ def getRadiationData(): |
152 | 157 |
radiationMonitorOnline = True |
153 | 158 |
|
154 | 159 |
if debugOption: |
155 |
- print "http request successful" |
|
156 | 160 |
#print content |
161 |
+ pass |
|
162 |
+ |
|
157 | 163 |
return content |
158 | 164 |
##end def |
159 | 165 |
|
... | ... |
@@ -236,6 +242,22 @@ def writeOutputDataFile(dData): |
236 | 242 |
return True |
237 | 243 |
## end def |
238 | 244 |
|
245 |
+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 |
+ # Write the string to the output data file for use by html documents. |
|
250 |
+ try: |
|
251 |
+ fc = open(_DATA_FORWARDING_FILE, "w") |
|
252 |
+ fc.write(sData) |
|
253 |
+ fc.close() |
|
254 |
+ except Exception, exError: |
|
255 |
+ print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
|
256 |
+ return False |
|
257 |
+ |
|
258 |
+ return True |
|
259 |
+##end def |
|
260 |
+ |
|
239 | 261 |
def updateDatabase(dData): |
240 | 262 |
""" |
241 | 263 |
Updates the rrdtool database by executing an rrdtool system command. |
... | ... |
@@ -343,17 +365,17 @@ def generateGraphs(): |
343 | 365 |
""" |
344 | 366 |
autoScale = False |
345 | 367 |
|
346 |
- createGraph('radGraph1', 'CPM', 'counts\ per\ minute', |
|
368 |
+ createGraph('24hr_cpm', 'CPM', 'counts\ per\ minute', |
|
347 | 369 |
'CPM\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
348 |
- createGraph('radGraph2', 'SvperHr', 'Sv\ per\ hour', |
|
370 |
+ createGraph('24hr_svperhr', 'SvperHr', 'Sv\ per\ hour', |
|
349 | 371 |
'Sv/Hr\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
350 |
- createGraph('radGraph3', 'CPM', 'counts\ per\ minute', |
|
372 |
+ createGraph('4wk_cpm', 'CPM', 'counts\ per\ minute', |
|
351 | 373 |
'CPM\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
352 |
- createGraph('radGraph4', 'SvperHr', 'Sv\ per\ hour', |
|
374 |
+ createGraph('4wk_svperhr', 'SvperHr', 'Sv\ per\ hour', |
|
353 | 375 |
'Sv/Hr\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
354 |
- createGraph('radGraph5', 'CPM', 'counts\ per\ minute', |
|
376 |
+ createGraph('12m_cpm', 'CPM', 'counts\ per\ minute', |
|
355 | 377 |
'CPM\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
356 |
- createGraph('radGraph6', 'SvperHr', 'Sv\ per\ hour', |
|
378 |
+ createGraph('12m_svperhr', 'SvperHr', 'Sv\ per\ hour', |
|
357 | 379 |
'Sv/Hr\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
358 | 380 |
##end def |
359 | 381 |
|
... | ... |
@@ -439,7 +461,10 @@ def main(): |
439 | 461 |
|
440 | 462 |
# If conversion successful, write data to output file. |
441 | 463 |
if result: |
464 |
+ writeForwardingFile(sData) |
|
442 | 465 |
writeOutputDataFile(dData) |
466 |
+ if debugOption: |
|
467 |
+ print "http request successful" |
|
443 | 468 |
|
444 | 469 |
# At the rrdtool database update interval, update the database. |
445 | 470 |
if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
1 | 1 |
old mode 100644 |
2 | 2 |
new mode 100755 |
... | ... |
@@ -49,19 +49,22 @@ _OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
49 | 49 |
|
50 | 50 |
### GLOBAL CONSTANTS ### |
51 | 51 |
|
52 |
-_DEFAULT_WEB_DATA_UPDATE_INTERVAL = 10 |
|
52 |
+_DEFAULT_DATA_REQUEST_INTERVAL = 10 # interval between data requests to radiation monitor |
|
53 | 53 |
_CHART_UPDATE_INTERVAL = 300 # defines how often the charts get updated |
54 | 54 |
_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated |
55 | 55 |
_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
56 |
+_MAX_RADIATION_MONITOR_OFFLINE_COUNT = 3 # max number of failed data requests allowed |
|
56 | 57 |
_CHART_WIDTH = 600 |
57 | 58 |
_CHART_HEIGHT = 150 |
59 |
+_DEFAULT_RADIATION_MONITOR_URL = "{your radiation monitor url}" |
|
58 | 60 |
|
59 | 61 |
### GLOBAL VARIABLES ### |
60 | 62 |
|
61 |
-webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
|
62 |
-deviceUrl = "{your device url}" # radiation monitor network address |
|
63 |
-deviceOnline = True |
|
64 | 63 |
debugOption = False |
64 |
+radiationMonitorOnline = True |
|
65 |
+radiationMonitorOfflineCount = 0 |
|
66 |
+dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL # web update frequency |
|
67 |
+radiationMonitorUrl = _DEFAULT_RADIATION_MONITOR_URL # radiation monitor network address |
|
65 | 68 |
|
66 | 69 |
|
67 | 70 |
### PRIVATE METHODS ### |
... | ... |
@@ -72,7 +75,7 @@ def getTimeStamp(): |
72 | 75 |
Parameters: none |
73 | 76 |
Returns string containing the time stamp. |
74 | 77 |
""" |
75 |
- return time.strftime( "%Y/%m/%d %T", time.localtime() ) |
|
78 |
+ return time.strftime( "%m/%d/%Y %T", time.localtime() ) |
|
76 | 79 |
##end def |
77 | 80 |
|
78 | 81 |
def setOfflineStatus(dData): |
... | ... |
@@ -82,13 +85,18 @@ def setOfflineStatus(dData): |
82 | 85 |
dData - dictionary object containing weather data |
83 | 86 |
Returns nothing. |
84 | 87 |
""" |
85 |
- global deviceOnline |
|
88 |
+ global radiationMonitorOnline, radiationMonitorOfflineCount |
|
89 |
+ |
|
90 |
+ radiationMonitorOfflineCount += 1 |
|
91 |
+ |
|
92 |
+ if radiationMonitorOfflineCount < _MAX_RADIATION_MONITOR_OFFLINE_COUNT: |
|
93 |
+ return |
|
86 | 94 |
|
87 | 95 |
# If the radiation monitor was previously online, then send a message |
88 | 96 |
# that we are now offline. |
89 |
- if deviceOnline: |
|
90 |
- print "%s: radmon offline" % getTimeStamp() |
|
91 |
- deviceOnline = False |
|
97 |
+ if radiationMonitorOnline: |
|
98 |
+ print "%s: radiation monitor offline" % getTimeStamp() |
|
99 |
+ radiationMonitorOnline = False |
|
92 | 100 |
|
93 | 101 |
# Set data items to blank. |
94 | 102 |
dData['UTC'] = '' |
... | ... |
@@ -104,40 +112,48 @@ def setOfflineStatus(dData): |
104 | 112 |
|
105 | 113 |
### PUBLIC METHODS ### |
106 | 114 |
|
107 |
-def getRadmonData(deviceUrl, HttpRequestTimeout): |
|
115 |
+def getRadiationData(): |
|
108 | 116 |
"""Send http request to radiation monitoring device. The response |
109 | 117 |
from the device contains the radiation data. The data is formatted |
110 | 118 |
as an html document. |
111 | 119 |
Parameters: |
112 |
- deviceUrl - url of radiation monitoring device |
|
120 |
+ radiationMonitorUrl - url of radiation monitoring device |
|
113 | 121 |
HttpRequesttimeout - how long to wait for device |
114 | 122 |
to respond to http request |
115 | 123 |
Returns a string containing the radiation data, or None if |
116 | 124 |
not successful. |
117 | 125 |
""" |
118 |
- global deviceOnline |
|
126 |
+ global radiationMonitorOnline, radiationMonitorOfflineCount |
|
119 | 127 |
|
120 | 128 |
try: |
121 |
- conn = urllib2.urlopen(deviceUrl + "/rdata", timeout=HttpRequestTimeout) |
|
129 |
+ conn = urllib2.urlopen(radiationMonitorUrl + "/rdata", |
|
130 |
+ timeout=_HTTP_REQUEST_TIMEOUT) |
|
131 |
+ |
|
132 |
+ # Format received data into a single string. |
|
133 |
+ content = "" |
|
134 |
+ for line in conn: |
|
135 |
+ content += line.strip() |
|
136 |
+ del conn |
|
137 |
+ |
|
122 | 138 |
except Exception, exError: |
123 | 139 |
# If no response is received from the device, then assume that |
124 | 140 |
# the device is down or unavailable over the network. In |
125 | 141 |
# that case set the status of the device to offline. |
126 | 142 |
if debugOption: |
127 |
- print "getRadmonData: %s\n" % exError |
|
143 |
+ print "http error: %s" % exError |
|
128 | 144 |
return None |
129 | 145 |
|
146 |
+ radiationMonitorOfflineCount = 0 |
|
147 |
+ |
|
130 | 148 |
# If the radiation monitor was previously offline, then send a message |
131 | 149 |
# that we are now online. |
132 |
- if not deviceOnline: |
|
133 |
- print "%s radmon online" % getTimeStamp() |
|
134 |
- deviceOnline = True |
|
135 |
- |
|
136 |
- # Format received data into a single string. |
|
137 |
- content = "" |
|
138 |
- for line in conn: |
|
139 |
- content += line.strip() |
|
140 |
- del conn |
|
150 |
+ if not radiationMonitorOnline: |
|
151 |
+ print "%s radiation monitor online" % getTimeStamp() |
|
152 |
+ radiationMonitorOnline = True |
|
153 |
+ |
|
154 |
+ if debugOption: |
|
155 |
+ print "http request successful" |
|
156 |
+ #print content |
|
141 | 157 |
return content |
142 | 158 |
##end def |
143 | 159 |
|
... | ... |
@@ -236,7 +252,7 @@ def updateDatabase(dData): |
236 | 252 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
237 | 253 |
(_RRD_FILE, dData['UTC'], dData['CPM'], Svvalue) |
238 | 254 |
if debugOption: |
239 |
- print "%s\n" % strCmd # DEBUG |
|
255 |
+ print "%s" % strCmd # DEBUG |
|
240 | 256 |
|
241 | 257 |
# Run the command as a subprocess. |
242 | 258 |
try: |
... | ... |
@@ -250,13 +266,22 @@ def updateDatabase(dData): |
250 | 266 |
return True |
251 | 267 |
##end def |
252 | 268 |
|
253 |
-def createGraph(fileName, dataItem, gLabel, gTitle, gStart, lower, upper, addTrend): |
|
269 |
+def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
|
270 |
+ lower, upper, addTrend, autoScale): |
|
254 | 271 |
"""Uses rrdtool to create a graph of specified weather data item. |
255 | 272 |
Parameters: |
256 | 273 |
fileName - name of graph image file |
257 | 274 |
dataItem - data item to be graphed |
258 |
- gTitle - a title for the graph |
|
259 |
- gStart - beginning time of the data to be graphed |
|
275 |
+ gLabel - string containing a graph label for the data item |
|
276 |
+ gTitle - string containing a title for the graph |
|
277 |
+ lower - lower bound for graph ordinate #NOT USED |
|
278 |
+ upper - upper bound for graph ordinate #NOT USED |
|
279 |
+ addTrend - 0, show only graph data |
|
280 |
+ 1, show only a trend line |
|
281 |
+ 2, show a trend line and the graph data |
|
282 |
+ autoScale - if True, then use vertical axis auto scaling |
|
283 |
+ (lower and upper parameters are ignored), otherwise use |
|
284 |
+ lower and upper parameters to set vertical axis scale |
|
260 | 285 |
Returns true if successful, false otherwise. |
261 | 286 |
""" |
262 | 287 |
gPath = _TMP_DIRECTORY + '/' + fileName + ".png" |
... | ... |
@@ -270,26 +295,29 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, lower, upper, addTre |
270 | 295 |
strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \ |
271 | 296 |
% (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT) |
272 | 297 |
|
273 |
- # Set the range of the chart ordinate dataum. |
|
298 |
+ # Set the range and scaling of the chart y-axis. |
|
274 | 299 |
if lower < upper: |
275 |
- strCmd += "-l %s -u %s " % (lower, upper) |
|
276 |
- else: |
|
277 |
- #strCmd += "-A -Y " |
|
278 |
- strCmd += "-Y " |
|
300 |
+ strCmd += "-l %s -u %s -r " % (lower, upper) |
|
301 |
+ elif autoScale: |
|
302 |
+ strCmd += "-A " |
|
303 |
+ strCmd += "-Y " |
|
279 | 304 |
|
280 | 305 |
# Set the chart ordinate label and chart title. |
281 | 306 |
strCmd += "-v %s -t %s " % (gLabel, gTitle) |
282 |
- |
|
307 |
+ |
|
283 | 308 |
# Show the data, or a moving average trend line over |
284 | 309 |
# the data, or both. |
285 |
- strCmd += "DEF:%s=%s:%s:LAST " % (dataItem, _RRD_FILE, dataItem) |
|
286 |
- |
|
287 |
- if addTrend == 0 or addTrend == 2: |
|
288 |
- strCmd += "LINE1:%s\#0400ff " % (dataItem) |
|
289 |
- if addTrend == 1 or addTrend == 2: |
|
290 |
- strCmd += "CDEF:smoothed=%s,%s,TREND LINE1:smoothed#ff0000" \ |
|
291 |
- % (dataItem, trendWindow[gStart]) |
|
292 |
- |
|
310 |
+ strCmd += "DEF:dSeries=%s:%s:LAST " % (_RRD_FILE, dataItem) |
|
311 |
+ if addTrend == 0: |
|
312 |
+ strCmd += "LINE1:dSeries#0400ff " |
|
313 |
+ elif addTrend == 1: |
|
314 |
+ strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
315 |
+ % trendWindow[gStart] |
|
316 |
+ elif addTrend == 2: |
|
317 |
+ strCmd += "LINE1:dSeries#0400ff " |
|
318 |
+ strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
319 |
+ % trendWindow[gStart] |
|
320 |
+ |
|
293 | 321 |
if debugOption: |
294 | 322 |
print "%s\n" % strCmd # DEBUG |
295 | 323 |
|
... | ... |
@@ -308,6 +336,27 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, lower, upper, addTre |
308 | 336 |
|
309 | 337 |
##end def |
310 | 338 |
|
339 |
+def generateGraphs(): |
|
340 |
+ """Generate graphs for display in html documents. |
|
341 |
+ Parameters: none |
|
342 |
+ Returns nothing. |
|
343 |
+ """ |
|
344 |
+ autoScale = False |
|
345 |
+ |
|
346 |
+ createGraph('radGraph1', 'CPM', 'counts\ per\ minute', |
|
347 |
+ 'CPM\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
|
348 |
+ createGraph('radGraph2', 'SvperHr', 'Sv\ per\ hour', |
|
349 |
+ 'Sv/Hr\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
|
350 |
+ createGraph('radGraph3', 'CPM', 'counts\ per\ minute', |
|
351 |
+ 'CPM\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
|
352 |
+ createGraph('radGraph4', 'SvperHr', 'Sv\ per\ hour', |
|
353 |
+ 'Sv/Hr\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
|
354 |
+ createGraph('radGraph5', 'CPM', 'counts\ per\ minute', |
|
355 |
+ 'CPM\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
|
356 |
+ createGraph('radGraph6', 'SvperHr', 'Sv\ per\ hour', |
|
357 |
+ 'Sv/Hr\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
|
358 |
+##end def |
|
359 |
+ |
|
311 | 360 |
def getCLarguments(): |
312 | 361 |
"""Get command line arguments. There are three possible arguments |
313 | 362 |
-d turns on debug mode |
... | ... |
@@ -315,7 +364,7 @@ def getCLarguments(): |
315 | 364 |
-u sets the url of the radiation monitoring device |
316 | 365 |
Returns nothing. |
317 | 366 |
""" |
318 |
- global debugOption, webUpdateInterval, deviceUrl |
|
367 |
+ global debugOption, dataRequestInterval, radiationMonitorUrl |
|
319 | 368 |
|
320 | 369 |
index = 1 |
321 | 370 |
while index < len(sys.argv): |
... | ... |
@@ -323,34 +372,21 @@ def getCLarguments(): |
323 | 372 |
debugOption = True |
324 | 373 |
elif sys.argv[index] == '-t': |
325 | 374 |
try: |
326 |
- webUpdateInterval = abs(int(sys.argv[index + 1])) |
|
375 |
+ dataRequestInterval = abs(int(sys.argv[index + 1])) |
|
327 | 376 |
except: |
328 | 377 |
print "invalid polling period" |
329 | 378 |
exit(-1) |
330 | 379 |
index += 1 |
331 | 380 |
elif sys.argv[index] == '-u': |
332 |
- deviceUrl = sys.argv[index + 1] |
|
381 |
+ radiationMonitorUrl = sys.argv[index + 1] |
|
333 | 382 |
index += 1 |
334 | 383 |
else: |
335 | 384 |
cmd_name = sys.argv[0].split('/') |
336 |
- print "Usage: %s {-v} {-d}" % cmd_name[-1] |
|
385 |
+ print "Usage: %s [-d] [-t seconds] [-u url}" % cmd_name[-1] |
|
337 | 386 |
exit(-1) |
338 | 387 |
index += 1 |
339 | 388 |
##end def |
340 | 389 |
|
341 |
-def generateGraphs(): |
|
342 |
- """Generate graphs for display in html documents. |
|
343 |
- Parameters: none |
|
344 |
- Returns nothing. |
|
345 |
- """ |
|
346 |
- createGraph('radGraph1', 'CPM', "'counts per minute'", "'CPM - Last 24 Hours'", 'end-1day', 0, 0, 2) |
|
347 |
- createGraph('radGraph2', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Last 24 Hours'", 'end-1day', 0, 0, 2) |
|
348 |
- createGraph('radGraph3', 'CPM', "'counts per minute'", "'CPM - Last 4 Weeks'", 'end-4weeks', 0, 0, 2) |
|
349 |
- createGraph('radGraph4', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Last 4 Weeks'", 'end-4weeks', 0, 0, 2) |
|
350 |
- createGraph('radGraph5', 'CPM', "'counts per minute'", "'CPM - Past Year'", 'end-12months', 0, 0, 2) |
|
351 |
- createGraph('radGraph6', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Past Year'", 'end-12months', 0, 0, 2) |
|
352 |
-##end def |
|
353 |
- |
|
354 | 390 |
def main(): |
355 | 391 |
"""Handles timing of events and acts as executive routine managing all other |
356 | 392 |
functions. |
... | ... |
@@ -358,11 +394,10 @@ def main(): |
358 | 394 |
Returns nothing. |
359 | 395 |
""" |
360 | 396 |
|
397 |
+ lastDataRequestTime = -1 # last time output JSON file updated |
|
361 | 398 |
lastChartUpdateTime = - 1 # last time charts generated |
362 | 399 |
lastDatabaseUpdateTime = -1 # last time the rrdtool database updated |
363 |
- lastWebUpdateTime = -1 # last time output JSON file updated |
|
364 | 400 |
dData = {} # dictionary object for temporary data storage |
365 |
- lsData = [] # list object for temporary data storage |
|
366 | 401 |
|
367 | 402 |
## Get command line arguments. |
368 | 403 |
getCLarguments() |
... | ... |
@@ -371,24 +406,25 @@ def main(): |
371 | 406 |
if not os.path.isdir(_TMP_DIRECTORY): |
372 | 407 |
os.makedirs(_TMP_DIRECTORY) |
373 | 408 |
|
374 |
- ## Exit with error if cannot find the rrdtool database file. |
|
409 |
+ ## Exit with error if rrdtool database does not exist. |
|
375 | 410 |
if not os.path.exists(_RRD_FILE): |
376 |
- print "cannot find rrdtool database file: terminating" |
|
411 |
+ print "cannot find rrdtool database\nuse createWeatherRrd script to" \ |
|
412 |
+ " create rrdtool database\n" |
|
377 | 413 |
exit(1) |
378 | 414 |
|
379 | 415 |
## main loop |
380 | 416 |
while True: |
381 | 417 |
|
382 |
- currentTime = time.time() |
|
418 |
+ currentTime = time.time() # get current time in seconds |
|
383 | 419 |
|
384 |
- # At the radiation device query interval request and process |
|
385 |
- # the data from the device. |
|
386 |
- if currentTime - lastWebUpdateTime > webUpdateInterval: |
|
387 |
- lastWebUpdateTime = currentTime |
|
420 |
+ # Every web update interval request data from the radiation |
|
421 |
+ # monitor and process the received data. |
|
422 |
+ if currentTime - lastDataRequestTime > dataRequestInterval: |
|
423 |
+ lastDataRequestTime = currentTime |
|
388 | 424 |
result = True |
389 | 425 |
|
390 | 426 |
# Get the data string from the device. |
391 |
- sData = getRadmonData(deviceUrl, _HTTP_REQUEST_TIMEOUT) |
|
427 |
+ sData = getRadiationData() |
|
392 | 428 |
if sData == None: |
393 | 429 |
setOfflineStatus(dData) |
394 | 430 |
result = False |
... | ... |
@@ -422,12 +458,12 @@ def main(): |
422 | 458 |
|
423 | 459 |
elapsedTime = time.time() - currentTime |
424 | 460 |
if debugOption: |
425 |
- print "web update: %6f sec\n" % elapsedTime |
|
426 |
- remainingTime = webUpdateInterval - elapsedTime |
|
427 |
- if remainingTime > 0: |
|
461 |
+ print "processing time: %6f sec\n" % elapsedTime |
|
462 |
+ remainingTime = dataRequestInterval - elapsedTime |
|
463 |
+ if remainingTime > 0.0: |
|
428 | 464 |
time.sleep(remainingTime) |
429 |
- |
|
430 | 465 |
## end while |
466 |
+ return |
|
431 | 467 |
## end def |
432 | 468 |
|
433 | 469 |
if __name__ == '__main__': |
... | ... |
@@ -59,7 +59,7 @@ _CHART_HEIGHT = 150 |
59 | 59 |
### GLOBAL VARIABLES ### |
60 | 60 |
|
61 | 61 |
webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
62 |
-deviceUrl = "http://73.157.139.23:4371" # radiation monitor network address |
|
62 |
+deviceUrl = "{your device url}" # radiation monitor network address |
|
63 | 63 |
deviceOnline = True |
64 | 64 |
debugOption = False |
65 | 65 |
|
... | ... |
@@ -50,7 +50,7 @@ _OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
50 | 50 |
### GLOBAL CONSTANTS ### |
51 | 51 |
|
52 | 52 |
_DEFAULT_WEB_DATA_UPDATE_INTERVAL = 10 |
53 |
-_CHART_UPDATE_INTERVAL = 60 # defines how often the charts get updated |
|
53 |
+_CHART_UPDATE_INTERVAL = 300 # defines how often the charts get updated |
|
54 | 54 |
_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated |
55 | 55 |
_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
56 | 56 |
_CHART_WIDTH = 600 |
... | ... |
@@ -59,7 +59,7 @@ _CHART_HEIGHT = 150 |
59 | 59 |
### GLOBAL VARIABLES ### |
60 | 60 |
|
61 | 61 |
webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
62 |
-deviceUrl = {your device URL} # radiation monitor network address |
|
62 |
+deviceUrl = "http://73.157.139.23:4371" # radiation monitor network address |
|
63 | 63 |
deviceOnline = True |
64 | 64 |
debugOption = False |
65 | 65 |
|
... | ... |
@@ -405,12 +405,11 @@ def main(): |
405 | 405 |
if result: |
406 | 406 |
writeOutputDataFile(dData) |
407 | 407 |
|
408 |
- # At the rrdtool database update interval, update the database. |
|
409 |
- if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
|
410 |
- lastDatabaseUpdateTime = currentTime |
|
411 |
- if result: |
|
412 |
- ## Update the round robin database with the parsed data. |
|
413 |
- result = updateDatabase(dData) |
|
408 |
+ # At the rrdtool database update interval, update the database. |
|
409 |
+ if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
|
410 |
+ lastDatabaseUpdateTime = currentTime |
|
411 |
+ ## Update the round robin database with the parsed data. |
|
412 |
+ result = updateDatabase(dData) |
|
414 | 413 |
|
415 | 414 |
# At the chart generation interval, generate charts. |
416 | 415 |
if currentTime - lastChartUpdateTime > _CHART_UPDATE_INTERVAL: |
... | ... |
@@ -282,7 +282,7 @@ def createGraph(fileName, dataItem, gLabel, gTitle, gStart, lower, upper, addTre |
282 | 282 |
|
283 | 283 |
# Show the data, or a moving average trend line over |
284 | 284 |
# the data, or both. |
285 |
- strCmd += "DEF:%s=%s:%s:AVERAGE " % (dataItem, _RRD_FILE, dataItem) |
|
285 |
+ strCmd += "DEF:%s=%s:%s:LAST " % (dataItem, _RRD_FILE, dataItem) |
|
286 | 286 |
|
287 | 287 |
if addTrend == 0 or addTrend == 2: |
288 | 288 |
strCmd += "LINE1:%s\#0400ff " % (dataItem) |
... | ... |
@@ -59,7 +59,7 @@ _CHART_HEIGHT = 150 |
59 | 59 |
### GLOBAL VARIABLES ### |
60 | 60 |
|
61 | 61 |
webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
62 |
-deviceUrl = "http://73.157.139.23:4371" # radiation monitor network address |
|
62 |
+deviceUrl = {your device URL} # radiation monitor network address |
|
63 | 63 |
deviceOnline = True |
64 | 64 |
debugOption = False |
65 | 65 |
|
... | ... |
@@ -42,8 +42,9 @@ import multiprocessing |
42 | 42 |
|
43 | 43 |
### FILE AND FOLDER LOCATIONS ### |
44 | 44 |
|
45 |
+_USER = os.environ['USER'] |
|
45 | 46 |
_TMP_DIRECTORY = "/tmp/radmon" # folder for charts and output data file |
46 |
-_RRD_FILE = "/home/{your user id}/database/radmonData.rrd" # database that stores the data |
|
47 |
+_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER # database that stores the data |
|
47 | 48 |
_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
48 | 49 |
|
49 | 50 |
### GLOBAL CONSTANTS ### |
... | ... |
@@ -52,11 +53,13 @@ _DEFAULT_WEB_DATA_UPDATE_INTERVAL = 10 |
52 | 53 |
_CHART_UPDATE_INTERVAL = 60 # defines how often the charts get updated |
53 | 54 |
_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated |
54 | 55 |
_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
56 |
+_CHART_WIDTH = 600 |
|
57 |
+_CHART_HEIGHT = 150 |
|
55 | 58 |
|
56 | 59 |
### GLOBAL VARIABLES ### |
57 | 60 |
|
58 | 61 |
webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
59 |
-deviceUrl = "{your URL}" # radiation monitor network address |
|
62 |
+deviceUrl = "http://73.157.139.23:4371" # radiation monitor network address |
|
60 | 63 |
deviceOnline = True |
61 | 64 |
debugOption = False |
62 | 65 |
|
... | ... |
@@ -211,7 +214,7 @@ def writeOutputDataFile(dData): |
211 | 214 |
print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
212 | 215 |
return False |
213 | 216 |
|
214 |
- if debugOption: |
|
217 |
+ if debugOption and 0: |
|
215 | 218 |
print sData |
216 | 219 |
|
217 | 220 |
return True |
... | ... |
@@ -247,7 +250,7 @@ def updateDatabase(dData): |
247 | 250 |
return True |
248 | 251 |
##end def |
249 | 252 |
|
250 |
-def createGraph(fileName, dataItem, gTitle, gStart): |
|
253 |
+def createGraph(fileName, dataItem, gLabel, gTitle, gStart, lower, upper, addTrend): |
|
251 | 254 |
"""Uses rrdtool to create a graph of specified weather data item. |
252 | 255 |
Parameters: |
253 | 256 |
fileName - name of graph image file |
... | ... |
@@ -257,30 +260,52 @@ def createGraph(fileName, dataItem, gTitle, gStart): |
257 | 260 |
Returns true if successful, false otherwise. |
258 | 261 |
""" |
259 | 262 |
gPath = _TMP_DIRECTORY + '/' + fileName + ".png" |
260 |
- |
|
261 |
- # Create the rrdtool graph command. |
|
262 |
- strFmt = ("rrdtool graph %s -a PNG -s %s -w 600 -h 150 " |
|
263 |
- ## "-l 50 -u 110 -r " |
|
264 |
- "-v %s -t %s " |
|
265 |
- "DEF:%s=%s:%s:AVERAGE " |
|
266 |
- "LINE2:%s\#0400ff:") |
|
267 |
- strCmd = strFmt % (gPath, gStart, dataItem, gTitle, dataItem, \ |
|
268 |
- _RRD_FILE, dataItem, dataItem) |
|
263 |
+ trendWindow = { 'end-1day': 7200, |
|
264 |
+ 'end-4weeks': 172800, |
|
265 |
+ 'end-12months': 604800 } |
|
266 |
+ |
|
267 |
+ # Format the rrdtool graph command. |
|
268 |
+ |
|
269 |
+ # Set chart start time, height, and width. |
|
270 |
+ strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \ |
|
271 |
+ % (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT) |
|
272 |
+ |
|
273 |
+ # Set the range of the chart ordinate dataum. |
|
274 |
+ if lower < upper: |
|
275 |
+ strCmd += "-l %s -u %s " % (lower, upper) |
|
276 |
+ else: |
|
277 |
+ #strCmd += "-A -Y " |
|
278 |
+ strCmd += "-Y " |
|
279 |
+ |
|
280 |
+ # Set the chart ordinate label and chart title. |
|
281 |
+ strCmd += "-v %s -t %s " % (gLabel, gTitle) |
|
282 |
+ |
|
283 |
+ # Show the data, or a moving average trend line over |
|
284 |
+ # the data, or both. |
|
285 |
+ strCmd += "DEF:%s=%s:%s:AVERAGE " % (dataItem, _RRD_FILE, dataItem) |
|
286 |
+ |
|
287 |
+ if addTrend == 0 or addTrend == 2: |
|
288 |
+ strCmd += "LINE1:%s\#0400ff " % (dataItem) |
|
289 |
+ if addTrend == 1 or addTrend == 2: |
|
290 |
+ strCmd += "CDEF:smoothed=%s,%s,TREND LINE1:smoothed#ff0000" \ |
|
291 |
+ % (dataItem, trendWindow[gStart]) |
|
292 |
+ |
|
269 | 293 |
if debugOption: |
270 | 294 |
print "%s\n" % strCmd # DEBUG |
271 | 295 |
|
272 |
- # Run the command as a subprocess. |
|
296 |
+ # Run the formatted rrdtool command as a subprocess. |
|
273 | 297 |
try: |
274 |
- result = subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \ |
|
298 |
+ result = subprocess.check_output(strCmd, \ |
|
299 |
+ stderr=subprocess.STDOUT, \ |
|
275 | 300 |
shell=True) |
276 | 301 |
except subprocess.CalledProcessError, exError: |
277 |
- print "rdtool graph failed: %s" % (exError.output) |
|
302 |
+ print "rrdtool graph failed: %s" % (exError.output) |
|
278 | 303 |
return False |
279 | 304 |
|
280 | 305 |
if debugOption: |
281 | 306 |
print "rrdtool graph: %s" % result |
282 |
- |
|
283 | 307 |
return True |
308 |
+ |
|
284 | 309 |
##end def |
285 | 310 |
|
286 | 311 |
def getCLarguments(): |
... | ... |
@@ -318,12 +343,12 @@ def generateGraphs(): |
318 | 343 |
Parameters: none |
319 | 344 |
Returns nothing. |
320 | 345 |
""" |
321 |
- createGraph('radGraph1', 'CPM', "'CPM - Last 24 Hours'", 'end-1day') |
|
322 |
- createGraph('radGraph2', 'SvperHr', "'Sv/Hr - Last 24 Hours'", 'end-1day') |
|
323 |
- createGraph('radGraph3', 'CPM', "'CPM - Last 4 Weeks'", 'end-4weeks') |
|
324 |
- createGraph('radGraph4', 'SvperHr', "'Sv/Hr - Last 4 Weeks'", 'end-4weeks') |
|
325 |
- createGraph('radGraph5', 'CPM', "'CPM - Past Year'", 'end-12months') |
|
326 |
- createGraph('radGraph6', 'SvperHr', "'Sv/Hr - Past Year'", 'end-12months') |
|
346 |
+ createGraph('radGraph1', 'CPM', "'counts per minute'", "'CPM - Last 24 Hours'", 'end-1day', 0, 0, 2) |
|
347 |
+ createGraph('radGraph2', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Last 24 Hours'", 'end-1day', 0, 0, 2) |
|
348 |
+ createGraph('radGraph3', 'CPM', "'counts per minute'", "'CPM - Last 4 Weeks'", 'end-4weeks', 0, 0, 2) |
|
349 |
+ createGraph('radGraph4', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Last 4 Weeks'", 'end-4weeks', 0, 0, 2) |
|
350 |
+ createGraph('radGraph5', 'CPM', "'counts per minute'", "'CPM - Past Year'", 'end-12months', 0, 0, 2) |
|
351 |
+ createGraph('radGraph6', 'SvperHr', "'Sv per hour'", "'Sv/Hr - Past Year'", 'end-12months', 0, 0, 2) |
|
327 | 352 |
##end def |
328 | 353 |
|
329 | 354 |
def main(): |
... | ... |
@@ -43,7 +43,7 @@ import multiprocessing |
43 | 43 |
### FILE AND FOLDER LOCATIONS ### |
44 | 44 |
|
45 | 45 |
_TMP_DIRECTORY = "/tmp/radmon" # folder for charts and output data file |
46 |
-_RRD_FILE = "/home/{user}/database/radmonData.rrd" # database that stores the data |
|
46 |
+_RRD_FILE = "/home/{your user id}/database/radmonData.rrd" # database that stores the data |
|
47 | 47 |
_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
48 | 48 |
|
49 | 49 |
### GLOBAL CONSTANTS ### |
... | ... |
@@ -177,8 +177,8 @@ def convertData(dData): |
177 | 177 |
local_sec = calendar.timegm(ts_utc) |
178 | 178 |
dData['UTC'] = local_sec |
179 | 179 |
|
180 |
+ dData['Mode'] = dData['Mode'].lower() |
|
180 | 181 |
dData['uSvPerHr'] = dData.pop('uSv/hr') |
181 |
- dData['Mode'] = dData.pop('Mode').lower() |
|
182 | 182 |
except Exception, exError: |
183 | 183 |
print "%s convertData: %s" % (getTimeStamp(), exError) |
184 | 184 |
result = False |
... | ... |
@@ -200,7 +200,7 @@ def writeOutputDataFile(dData): |
200 | 200 |
sData = '[{' |
201 | 201 |
for key in dData: |
202 | 202 |
sData += "\"%s\":\"%s\"," % (key, dData[key]) |
203 |
- sData = sData[:-1] + '}]\n' |
|
203 |
+ sData = sData[:-1] + '}]' |
|
204 | 204 |
|
205 | 205 |
# Write the string to the output data file for use by html documents. |
206 | 206 |
try: |
... | ... |
@@ -56,9 +56,11 @@ _HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP reques |
56 | 56 |
### GLOBAL VARIABLES ### |
57 | 57 |
|
58 | 58 |
webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
59 |
-deviceUrl = "http://192.168.1.8" # radiation monitor network address |
|
59 |
+deviceUrl = "{your URL}" # radiation monitor network address |
|
60 |
+deviceOnline = True |
|
60 | 61 |
debugOption = False |
61 | 62 |
|
63 |
+ |
|
62 | 64 |
### PRIVATE METHODS ### |
63 | 65 |
|
64 | 66 |
def getTimeStamp(): |
... | ... |
@@ -70,89 +72,97 @@ def getTimeStamp(): |
70 | 72 |
return time.strftime( "%Y/%m/%d %T", time.localtime() ) |
71 | 73 |
##end def |
72 | 74 |
|
73 |
-def sendOffLineStatusMessage(): |
|
74 |
- """Sets the status of the the upstream device to "offline" and sends |
|
75 |
+def setOfflineStatus(dData): |
|
76 |
+ """Set the status of the the upstream device to "offline" and sends |
|
75 | 77 |
blank data to the downstream clients. |
76 |
- Parameters: none |
|
78 |
+ Parameters: |
|
79 |
+ dData - dictionary object containing weather data |
|
77 | 80 |
Returns nothing. |
78 | 81 |
""" |
79 |
- sTmp = "\"date\":\"\",\"CPS\":\"\",\"CPM\":\"\"," \ |
|
80 |
- "\"uSvPerHr\":\"\",\"Mode\":\"\",\"status\":\"offline\"" |
|
81 |
- |
|
82 |
- lsTmp = sTmp.split(',') |
|
83 |
- lsTmp[0] = "\"date\":\"%s\"" % getTimeStamp() |
|
84 |
- writeOutputDataFile(lsTmp) |
|
82 |
+ global deviceOnline |
|
83 |
+ |
|
84 |
+ # If the radiation monitor was previously online, then send a message |
|
85 |
+ # that we are now offline. |
|
86 |
+ if deviceOnline: |
|
87 |
+ print "%s: radmon offline" % getTimeStamp() |
|
88 |
+ deviceOnline = False |
|
89 |
+ |
|
90 |
+ # Set data items to blank. |
|
91 |
+ dData['UTC'] = '' |
|
92 |
+ dData['CPM'] = '' |
|
93 |
+ dData['CPS'] = '' |
|
94 |
+ dData['uSvPerHr'] = '' |
|
95 |
+ dData['Mode'] = '' |
|
96 |
+ dData['status'] = 'offline' |
|
97 |
+ |
|
98 |
+ writeOutputDataFile(dData) |
|
85 | 99 |
return |
86 | 100 |
##end def |
87 | 101 |
|
88 | 102 |
### PUBLIC METHODS ### |
89 | 103 |
|
90 | 104 |
def getRadmonData(deviceUrl, HttpRequestTimeout): |
91 |
- """Send http request to radiation monitoring device. The response |
|
92 |
- from the device contains the radiation data. The data is formatted |
|
93 |
- as an html document. |
|
94 |
- Parameters: |
|
95 |
- deviceUrl - url of radiation monitoring device |
|
96 |
- HttpRequesttimeout - how long to wait for device |
|
97 |
- to respond to http request |
|
98 |
- Returns a string containing the radiation data, or None if |
|
99 |
- not successful. |
|
100 |
- """ |
|
101 |
- content = "" |
|
102 |
- try: |
|
103 |
- conn = urllib2.urlopen(deviceUrl + "/jsdata", timeout=HttpRequestTimeout) |
|
104 |
- except Exception, exError: |
|
105 |
- # If no response is received from the device, then assume that |
|
106 |
- # the device is down or unavailable over the network. In |
|
107 |
- # that case set the status of the device to offline. |
|
108 |
- print "%s: device offline: %s" % \ |
|
109 |
- (getTimeStamp(), exError) |
|
110 |
- return None |
|
111 |
- else: |
|
112 |
- for line in conn: |
|
113 |
- content += line.strip() |
|
114 |
- if len(content) == 0: |
|
115 |
- print "%s: HTTP download failed: null content" % \ |
|
116 |
- (getTimeStamp()) |
|
117 |
- return None |
|
118 |
- del conn |
|
119 |
- return content |
|
105 |
+ """Send http request to radiation monitoring device. The response |
|
106 |
+ from the device contains the radiation data. The data is formatted |
|
107 |
+ as an html document. |
|
108 |
+ Parameters: |
|
109 |
+ deviceUrl - url of radiation monitoring device |
|
110 |
+ HttpRequesttimeout - how long to wait for device |
|
111 |
+ to respond to http request |
|
112 |
+ Returns a string containing the radiation data, or None if |
|
113 |
+ not successful. |
|
114 |
+ """ |
|
115 |
+ global deviceOnline |
|
116 |
+ |
|
117 |
+ try: |
|
118 |
+ conn = urllib2.urlopen(deviceUrl + "/rdata", timeout=HttpRequestTimeout) |
|
119 |
+ except Exception, exError: |
|
120 |
+ # If no response is received from the device, then assume that |
|
121 |
+ # the device is down or unavailable over the network. In |
|
122 |
+ # that case set the status of the device to offline. |
|
123 |
+ if debugOption: |
|
124 |
+ print "getRadmonData: %s\n" % exError |
|
125 |
+ return None |
|
126 |
+ |
|
127 |
+ # If the radiation monitor was previously offline, then send a message |
|
128 |
+ # that we are now online. |
|
129 |
+ if not deviceOnline: |
|
130 |
+ print "%s radmon online" % getTimeStamp() |
|
131 |
+ deviceOnline = True |
|
132 |
+ |
|
133 |
+ # Format received data into a single string. |
|
134 |
+ content = "" |
|
135 |
+ for line in conn: |
|
136 |
+ content += line.strip() |
|
137 |
+ del conn |
|
138 |
+ return content |
|
120 | 139 |
##end def |
121 | 140 |
|
122 |
-def parseDataString(sData, lsData, dData): |
|
141 |
+def parseDataString(sData, dData): |
|
123 | 142 |
"""Parse the radiation data JSON string from the radiation |
124 | 143 |
monitoring device into its component parts. |
125 | 144 |
Parameters: |
126 | 145 |
sData - the string containing the data to be parsed |
127 |
- lsData - a list object to contain the parsed data items |
|
128 | 146 |
dData - a dictionary object to contain the parsed data items |
129 | 147 |
Returns true if successful, false otherwise. |
130 | 148 |
""" |
131 |
- # Clear data array in preparation for loading reformatted data. |
|
132 |
- while len(lsData) > 0: |
|
133 |
- elmt = lsData.pop(0) |
|
134 |
- |
|
135 | 149 |
try: |
136 |
- dTmp = json.loads(sData[1:-1]) |
|
137 |
- sTmp = dTmp['radmon'].encode('ascii', 'ignore') |
|
150 |
+ sTmp = sData[2:-2] |
|
138 | 151 |
lsTmp = sTmp.split(',') |
139 |
- lsData.extend(lsTmp) |
|
140 | 152 |
except Exception, exError: |
141 |
- print "%s parse failed: %s" % (getTimeStamp(), exError) |
|
153 |
+ print "%s parseDataString: %s" % (getTimeStamp(), exError) |
|
142 | 154 |
return False |
143 | 155 |
|
144 |
- # Since the device responded, set the status to online. |
|
145 |
- lsData.insert(-2, "status=online") |
|
146 |
- |
|
147 | 156 |
# Load the parsed data into a dictionary for easy access. |
148 |
- for item in lsData: |
|
157 |
+ for item in lsTmp: |
|
149 | 158 |
if "=" in item: |
150 | 159 |
dData[item.split('=')[0]] = item.split('=')[1] |
160 |
+ dData['status'] = 'online' |
|
151 | 161 |
|
152 | 162 |
return True |
153 | 163 |
##end def |
154 | 164 |
|
155 |
-def convertData(lsData, dData): |
|
165 |
+def convertData(dData): |
|
156 | 166 |
"""Convert individual radiation data items as necessary. |
157 | 167 |
Parameters: |
158 | 168 |
lsData - a list object containing the radiation data |
... | ... |
@@ -166,47 +176,44 @@ def convertData(lsData, dData): |
166 | 176 |
ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y") |
167 | 177 |
local_sec = calendar.timegm(ts_utc) |
168 | 178 |
dData['UTC'] = local_sec |
169 |
- except: |
|
170 |
- print "%s invalid time: %s" % (getTimeStamp(), utc) |
|
171 |
- result = False |
|
172 |
- |
|
173 |
- # Clear data array in preparation for loading reformatted data. |
|
174 |
- while len(lsData) > 0: |
|
175 |
- elmt = lsData.pop(0) |
|
176 | 179 |
|
177 |
- lsData.append("\"UTC\":\"%s\"" % dData['UTC']) |
|
178 |
- lsData.append("\"CPS\":\"%s\"" % dData['CPS']) |
|
179 |
- lsData.append("\"CPM\":\"%s\"" % dData['CPM']) |
|
180 |
- lsData.append("\"uSvPerHr\":\"%s\"" % dData['uSv/hr']) |
|
181 |
- lsData.append("\"Mode\":\"%s\"" % dData['Mode'].lower()) |
|
182 |
- lsData.append("\"status\":\"%s\"" % dData['status']) |
|
180 |
+ dData['uSvPerHr'] = dData.pop('uSv/hr') |
|
181 |
+ dData['Mode'] = dData.pop('Mode').lower() |
|
182 |
+ except Exception, exError: |
|
183 |
+ print "%s convertData: %s" % (getTimeStamp(), exError) |
|
184 |
+ result = False |
|
183 | 185 |
|
184 | 186 |
return result |
185 | 187 |
##end def |
186 | 188 |
|
187 |
-def writeOutputDataFile(lsData): |
|
189 |
+def writeOutputDataFile(dData): |
|
188 | 190 |
"""Convert individual weather string data items as necessary. |
189 | 191 |
Parameters: |
190 | 192 |
lsData - a list object containing the data to be written |
191 | 193 |
to the JSON file |
192 | 194 |
Returns true if successful, false otherwise. |
193 | 195 |
""" |
194 |
- # Convert the list object to a string. |
|
195 |
- sTmp = ','.join(lsData) |
|
196 |
+ # Set date to current time and data |
|
197 |
+ dData['date'] = getTimeStamp() |
|
196 | 198 |
|
197 |
- # Apply JSON formatting to the string and write it to a |
|
198 |
- # file for use by html documents. |
|
199 |
- sData = "[{%s}]\n" % (sTmp) |
|
199 |
+ # Format the weather data as string using java script object notation. |
|
200 |
+ sData = '[{' |
|
201 |
+ for key in dData: |
|
202 |
+ sData += "\"%s\":\"%s\"," % (key, dData[key]) |
|
203 |
+ sData = sData[:-1] + '}]\n' |
|
200 | 204 |
|
205 |
+ # Write the string to the output data file for use by html documents. |
|
201 | 206 |
try: |
202 | 207 |
fc = open(_OUTPUT_DATA_FILE, "w") |
203 | 208 |
fc.write(sData) |
204 | 209 |
fc.close() |
205 | 210 |
except Exception, exError: |
206 |
- print "%s: write to JSON file failed: %s" % \ |
|
207 |
- (getTimeStamp(), exError) |
|
211 |
+ print "%s writeOutputDataFile: %s" % (getTimeStamp(), exError) |
|
208 | 212 |
return False |
209 | 213 |
|
214 |
+ if debugOption: |
|
215 |
+ print sData |
|
216 |
+ |
|
210 | 217 |
return True |
211 | 218 |
## end def |
212 | 219 |
|
... | ... |
@@ -220,7 +227,7 @@ def updateDatabase(dData): |
220 | 227 |
Returns true if successful, false otherwise. |
221 | 228 |
""" |
222 | 229 |
# The RR database stores whole units, so convert uSv to Sv. |
223 |
- Svvalue = float(dData['uSv/hr']) * 1.0E-06 # convert micro-Sieverts to Sieverts |
|
230 |
+ Svvalue = float(dData['uSvPerHr']) * 1.0E-06 # convert micro-Sieverts to Sieverts |
|
224 | 231 |
|
225 | 232 |
# Create the rrdtool update command. |
226 | 233 |
strCmd = "rrdtool update %s %s:%s:%s" % \ |
... | ... |
@@ -328,7 +335,7 @@ def main(): |
328 | 335 |
|
329 | 336 |
lastChartUpdateTime = - 1 # last time charts generated |
330 | 337 |
lastDatabaseUpdateTime = -1 # last time the rrdtool database updated |
331 |
- lastWebDataUpdateTime = -1 # last time output JSON file updated |
|
338 |
+ lastWebUpdateTime = -1 # last time output JSON file updated |
|
332 | 339 |
dData = {} # dictionary object for temporary data storage |
333 | 340 |
lsData = [] # list object for temporary data storage |
334 | 341 |
|
... | ... |
@@ -351,28 +358,27 @@ def main(): |
351 | 358 |
|
352 | 359 |
# At the radiation device query interval request and process |
353 | 360 |
# the data from the device. |
354 |
- if currentTime - lastWebDataUpdateTime > webUpdateInterval: |
|
355 |
- llastWebDataUpdateTime = currentTime |
|
361 |
+ if currentTime - lastWebUpdateTime > webUpdateInterval: |
|
362 |
+ lastWebUpdateTime = currentTime |
|
356 | 363 |
result = True |
357 | 364 |
|
358 | 365 |
# Get the data string from the device. |
359 | 366 |
sData = getRadmonData(deviceUrl, _HTTP_REQUEST_TIMEOUT) |
360 | 367 |
if sData == None: |
361 |
- sendOffLineStatusMessage() |
|
368 |
+ setOfflineStatus(dData) |
|
362 | 369 |
result = False |
363 | 370 |
|
364 | 371 |
# If successful parse the data. |
365 | 372 |
if result: |
366 |
- result = parseDataString(sData, lsData, dData) |
|
373 |
+ result = parseDataString(sData, dData) |
|
367 | 374 |
|
368 | 375 |
# If parsing successful, convert the data. |
369 | 376 |
if result: |
370 |
- result = convertData(lsData, dData) |
|
377 |
+ result = convertData(dData) |
|
371 | 378 |
|
372 | 379 |
# If conversion successful, write data to output file. |
373 | 380 |
if result: |
374 |
- lsData[0] = "\"date\":\"%s\"" % getTimeStamp() |
|
375 |
- writeOutputDataFile(lsData) |
|
381 |
+ writeOutputDataFile(dData) |
|
376 | 382 |
|
377 | 383 |
# At the rrdtool database update interval, update the database. |
378 | 384 |
if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
... | ... |
@@ -40,19 +40,23 @@ import os |
40 | 40 |
import json |
41 | 41 |
import multiprocessing |
42 | 42 |
|
43 |
-## Define constants |
|
44 |
-_TMP_DIRECTORY = "/tmp/radmon" |
|
45 |
-_RRD_FILE = "/home/pi/database/radmonData.rrd" # the file that stores the data |
|
46 |
-_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" |
|
47 |
- |
|
48 |
-_WEB_DATA_UPDATE_INTERVAL = 1 |
|
49 |
-_CHART_UPDATE_INTERVAL = 60 |
|
50 |
-_DATABASE_UPDATE_INTERVAL = 30 |
|
43 |
+ ### FILE AND FOLDER LOCATIONS ### |
|
44 |
+ |
|
45 |
+_TMP_DIRECTORY = "/tmp/radmon" # folder for charts and output data file |
|
46 |
+_RRD_FILE = "/home/{user}/database/radmonData.rrd" # database that stores the data |
|
47 |
+_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" # output file used by HTML docs |
|
48 |
+ |
|
49 |
+ ### GLOBAL CONSTANTS ### |
|
50 |
+ |
|
51 |
+_DEFAULT_WEB_DATA_UPDATE_INTERVAL = 10 |
|
52 |
+_CHART_UPDATE_INTERVAL = 60 # defines how often the charts get updated |
|
53 |
+_DATABASE_UPDATE_INTERVAL = 30 # defines how often the database gets updated |
|
51 | 54 |
_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
52 | 55 |
|
53 |
-## Define run time options |
|
54 |
-deviceQueryInterval = 5.0 # period defines how often the RR database gets updated |
|
55 |
-deviceUrl = "http://{IP address}:{port}/jsdata" # radiation monitor network address |
|
56 |
+ ### GLOBAL VARIABLES ### |
|
57 |
+ |
|
58 |
+webUpdateInterval = _DEFAULT_WEB_DATA_UPDATE_INTERVAL # web update frequency |
|
59 |
+deviceUrl = "http://192.168.1.8" # radiation monitor network address |
|
56 | 60 |
debugOption = False |
57 | 61 |
|
58 | 62 |
### PRIVATE METHODS ### |
... | ... |
@@ -66,23 +70,24 @@ def getTimeStamp(): |
66 | 70 |
return time.strftime( "%Y/%m/%d %T", time.localtime() ) |
67 | 71 |
##end def |
68 | 72 |
|
69 |
-def sendOffLineStatusMsg(): |
|
73 |
+def sendOffLineStatusMessage(): |
|
70 | 74 |
"""Sets the status of the the upstream device to "offline" and sends |
71 | 75 |
blank data to the downstream clients. |
72 | 76 |
Parameters: none |
73 | 77 |
Returns nothing. |
74 | 78 |
""" |
75 |
- sTmp = "\"time\":\"\",\"CPS\":\"\",\"CPM\":\"\"," \ |
|
79 |
+ sTmp = "\"date\":\"\",\"CPS\":\"\",\"CPM\":\"\"," \ |
|
76 | 80 |
"\"uSvPerHr\":\"\",\"Mode\":\"\",\"status\":\"offline\"" |
77 | 81 |
|
78 | 82 |
lsTmp = sTmp.split(',') |
79 |
- writeJSONfile(lsTmp) |
|
83 |
+ lsTmp[0] = "\"date\":\"%s\"" % getTimeStamp() |
|
84 |
+ writeOutputDataFile(lsTmp) |
|
80 | 85 |
return |
81 | 86 |
##end def |
82 | 87 |
|
83 | 88 |
### PUBLIC METHODS ### |
84 | 89 |
|
85 |
-def getDataString(deviceUrl, HttpRequestTimeout): |
|
90 |
+def getRadmonData(deviceUrl, HttpRequestTimeout): |
|
86 | 91 |
"""Send http request to radiation monitoring device. The response |
87 | 92 |
from the device contains the radiation data. The data is formatted |
88 | 93 |
as an html document. |
... | ... |
@@ -95,14 +100,13 @@ def getDataString(deviceUrl, HttpRequestTimeout): |
95 | 100 |
""" |
96 | 101 |
content = "" |
97 | 102 |
try: |
98 |
- conn = urllib2.urlopen(deviceUrl, timeout=HttpRequestTimeout) |
|
103 |
+ conn = urllib2.urlopen(deviceUrl + "/jsdata", timeout=HttpRequestTimeout) |
|
99 | 104 |
except Exception, exError: |
100 | 105 |
# If no response is received from the device, then assume that |
101 | 106 |
# the device is down or unavailable over the network. In |
102 | 107 |
# that case set the status of the device to offline. |
103 | 108 |
print "%s: device offline: %s" % \ |
104 | 109 |
(getTimeStamp(), exError) |
105 |
- sendOffLineStatusMsg() |
|
106 | 110 |
return None |
107 | 111 |
else: |
108 | 112 |
for line in conn: |
... | ... |
@@ -112,9 +116,6 @@ def getDataString(deviceUrl, HttpRequestTimeout): |
112 | 116 |
(getTimeStamp()) |
113 | 117 |
return None |
114 | 118 |
del conn |
115 |
- |
|
116 |
- if debugOption: |
|
117 |
- print "%s\n" % content # DEBUG |
|
118 | 119 |
return content |
119 | 120 |
##end def |
120 | 121 |
|
... | ... |
@@ -148,9 +149,6 @@ def parseDataString(sData, lsData, dData): |
148 | 149 |
if "=" in item: |
149 | 150 |
dData[item.split('=')[0]] = item.split('=')[1] |
150 | 151 |
|
151 |
- if debugOption and 0: |
|
152 |
- print lsData |
|
153 |
- print dData |
|
154 | 152 |
return True |
155 | 153 |
##end def |
156 | 154 |
|
... | ... |
@@ -186,7 +184,7 @@ def convertData(lsData, dData): |
186 | 184 |
return result |
187 | 185 |
##end def |
188 | 186 |
|
189 |
-def writeJSONfile(lsData): |
|
187 |
+def writeOutputDataFile(lsData): |
|
190 | 188 |
"""Convert individual weather string data items as necessary. |
191 | 189 |
Parameters: |
192 | 190 |
lsData - a list object containing the data to be written |
... | ... |
@@ -198,16 +196,17 @@ def writeJSONfile(lsData): |
198 | 196 |
|
199 | 197 |
# Apply JSON formatting to the string and write it to a |
200 | 198 |
# file for use by html documents. |
201 |
- strJSON = "[{%s}]\n" % (sTmp) |
|
199 |
+ sData = "[{%s}]\n" % (sTmp) |
|
202 | 200 |
|
203 | 201 |
try: |
204 | 202 |
fc = open(_OUTPUT_DATA_FILE, "w") |
205 |
- fc.write(strJSON) |
|
203 |
+ fc.write(sData) |
|
206 | 204 |
fc.close() |
207 | 205 |
except Exception, exError: |
208 | 206 |
print "%s: write to JSON file failed: %s" % \ |
209 | 207 |
(getTimeStamp(), exError) |
210 | 208 |
return False |
209 |
+ |
|
211 | 210 |
return True |
212 | 211 |
## end def |
213 | 212 |
|
... | ... |
@@ -284,7 +283,7 @@ def getCLarguments(): |
284 | 283 |
-u sets the url of the radiation monitoring device |
285 | 284 |
Returns nothing. |
286 | 285 |
""" |
287 |
- global debugOption, deviceQueryInterval, deviceURL |
|
286 |
+ global debugOption, webUpdateInterval, deviceUrl |
|
288 | 287 |
|
289 | 288 |
index = 1 |
290 | 289 |
while index < len(sys.argv): |
... | ... |
@@ -292,13 +291,13 @@ def getCLarguments(): |
292 | 291 |
debugOption = True |
293 | 292 |
elif sys.argv[index] == '-t': |
294 | 293 |
try: |
295 |
- deviceQueryInterval = abs(int(sys.argv[index + 1])) |
|
294 |
+ webUpdateInterval = abs(int(sys.argv[index + 1])) |
|
296 | 295 |
except: |
297 | 296 |
print "invalid polling period" |
298 | 297 |
exit(-1) |
299 | 298 |
index += 1 |
300 | 299 |
elif sys.argv[index] == '-u': |
301 |
- deviceURL = sys.argv[index + 1] |
|
300 |
+ deviceUrl = sys.argv[index + 1] |
|
302 | 301 |
index += 1 |
303 | 302 |
else: |
304 | 303 |
cmd_name = sys.argv[0].split('/') |
... | ... |
@@ -326,10 +325,10 @@ def main(): |
326 | 325 |
Parameters: none |
327 | 326 |
Returns nothing. |
328 | 327 |
""" |
328 |
+ |
|
329 | 329 |
lastChartUpdateTime = - 1 # last time charts generated |
330 | 330 |
lastDatabaseUpdateTime = -1 # last time the rrdtool database updated |
331 | 331 |
lastWebDataUpdateTime = -1 # last time output JSON file updated |
332 |
- lastDeviceQueryTime = -1 # last time radiation device queried for data |
|
333 | 332 |
dData = {} # dictionary object for temporary data storage |
334 | 333 |
lsData = [] # list object for temporary data storage |
335 | 334 |
|
... | ... |
@@ -352,13 +351,14 @@ def main(): |
352 | 351 |
|
353 | 352 |
# At the radiation device query interval request and process |
354 | 353 |
# the data from the device. |
355 |
- if currentTime - lastDeviceQueryTime > deviceQueryInterval: |
|
356 |
- lastDeviceQueryTime = currentTime |
|
354 |
+ if currentTime - lastWebDataUpdateTime > webUpdateInterval: |
|
355 |
+ llastWebDataUpdateTime = currentTime |
|
357 | 356 |
result = True |
358 | 357 |
|
359 | 358 |
# Get the data string from the device. |
360 |
- sData = getDataString(deviceUrl, _HTTP_REQUEST_TIMEOUT) |
|
359 |
+ sData = getRadmonData(deviceUrl, _HTTP_REQUEST_TIMEOUT) |
|
361 | 360 |
if sData == None: |
361 |
+ sendOffLineStatusMessage() |
|
362 | 362 |
result = False |
363 | 363 |
|
364 | 364 |
# If successful parse the data. |
... | ... |
@@ -368,19 +368,16 @@ def main(): |
368 | 368 |
# If parsing successful, convert the data. |
369 | 369 |
if result: |
370 | 370 |
result = convertData(lsData, dData) |
371 |
- |
|
372 |
- # At the web update interval, update the JSON file used to pass |
|
373 |
- # radiation data to html documents. |
|
374 |
- if currentTime - lastWebDataUpdateTime > _WEB_DATA_UPDATE_INTERVAL: |
|
375 |
- lastWebDataUpdateTime = currentTime |
|
371 |
+ |
|
372 |
+ # If conversion successful, write data to output file. |
|
376 | 373 |
if result: |
377 |
- lsData[0] = "\"time\":\"%s\"" % getTimeStamp() |
|
378 |
- writeJSONfile(lsData) |
|
374 |
+ lsData[0] = "\"date\":\"%s\"" % getTimeStamp() |
|
375 |
+ writeOutputDataFile(lsData) |
|
379 | 376 |
|
380 | 377 |
# At the rrdtool database update interval, update the database. |
381 | 378 |
if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
379 |
+ lastDatabaseUpdateTime = currentTime |
|
382 | 380 |
if result: |
383 |
- lastDatabaseUpdateTime = currentTime |
|
384 | 381 |
## Update the round robin database with the parsed data. |
385 | 382 |
result = updateDatabase(dData) |
386 | 383 |
|
... | ... |
@@ -395,8 +392,8 @@ def main(): |
395 | 392 |
|
396 | 393 |
elapsedTime = time.time() - currentTime |
397 | 394 |
if debugOption: |
398 |
- print "processing time: %s\n" % elapsedTime |
|
399 |
- remainingTime = _WEB_DATA_UPDATE_INTERVAL - elapsedTime |
|
395 |
+ print "web update: %6f sec\n" % elapsedTime |
|
396 |
+ remainingTime = webUpdateInterval - elapsedTime |
|
400 | 397 |
if remainingTime > 0: |
401 | 398 |
time.sleep(remainingTime) |
402 | 399 |
|
... | ... |
@@ -52,7 +52,7 @@ _HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP reques |
52 | 52 |
|
53 | 53 |
## Define run time options |
54 | 54 |
deviceQueryInterval = 5.0 # period defines how often the RR database gets updated |
55 |
-deviceUrl = "http://73.157.139.23:4371/jsdata" # radiation monitor network address |
|
55 |
+deviceUrl = "http://{IP address}:{port}/jsdata" # radiation monitor network address |
|
56 | 56 |
debugOption = False |
57 | 57 |
|
58 | 58 |
### PRIVATE METHODS ### |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,408 @@ |
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. |
|
4 |
+# |
|
5 |
+# Module: radmonAgent.py |
|
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: |
|
11 |
+# - conversion of data items |
|
12 |
+# - update a round robin (rrdtool) database with the radiation data |
|
13 |
+# - periodically generate graphic charts for display in html documents |
|
14 |
+# - forward the radiation data to other services |
|
15 |
+# - write the processed weather data to a JSON file for use by html documents |
|
16 |
+# |
|
17 |
+# Copyright 2015 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 15 Sep 2015 by J L Owrey |
|
33 |
+# |
|
34 |
+import urllib2 |
|
35 |
+import time |
|
36 |
+import calendar |
|
37 |
+import subprocess |
|
38 |
+import sys |
|
39 |
+import os |
|
40 |
+import json |
|
41 |
+import multiprocessing |
|
42 |
+ |
|
43 |
+## Define constants |
|
44 |
+_TMP_DIRECTORY = "/tmp/radmon" |
|
45 |
+_RRD_FILE = "/home/pi/database/radmonData.rrd" # the file that stores the data |
|
46 |
+_OUTPUT_DATA_FILE = "/tmp/radmon/radmonData.js" |
|
47 |
+ |
|
48 |
+_WEB_DATA_UPDATE_INTERVAL = 1 |
|
49 |
+_CHART_UPDATE_INTERVAL = 60 |
|
50 |
+_DATABASE_UPDATE_INTERVAL = 30 |
|
51 |
+_HTTP_REQUEST_TIMEOUT = 5 # number seconds to wait for a response to HTTP request |
|
52 |
+ |
|
53 |
+## Define run time options |
|
54 |
+deviceQueryInterval = 5.0 # period defines how often the RR database gets updated |
|
55 |
+deviceUrl = "http://73.157.139.23:4371/jsdata" # radiation monitor network address |
|
56 |
+debugOption = False |
|
57 |
+ |
|
58 |
+ ### PRIVATE METHODS ### |
|
59 |
+ |
|
60 |
+def getTimeStamp(): |
|
61 |
+ """ |
|
62 |
+ Sets the error message time stamp to the local system time. |
|
63 |
+ Parameters: none |
|
64 |
+ Returns string containing the time stamp. |
|
65 |
+ """ |
|
66 |
+ return time.strftime( "%Y/%m/%d %T", time.localtime() ) |
|
67 |
+##end def |
|
68 |
+ |
|
69 |
+def sendOffLineStatusMsg(): |
|
70 |
+ """Sets the status of the the upstream device to "offline" and sends |
|
71 |
+ blank data to the downstream clients. |
|
72 |
+ Parameters: none |
|
73 |
+ Returns nothing. |
|
74 |
+ """ |
|
75 |
+ sTmp = "\"time\":\"\",\"CPS\":\"\",\"CPM\":\"\"," \ |
|
76 |
+ "\"uSvPerHr\":\"\",\"Mode\":\"\",\"status\":\"offline\"" |
|
77 |
+ |
|
78 |
+ lsTmp = sTmp.split(',') |
|
79 |
+ writeJSONfile(lsTmp) |
|
80 |
+ return |
|
81 |
+##end def |
|
82 |
+ |
|
83 |
+ ### PUBLIC METHODS ### |
|
84 |
+ |
|
85 |
+def getDataString(deviceUrl, HttpRequestTimeout): |
|
86 |
+ """Send http request to radiation monitoring device. The response |
|
87 |
+ from the device contains the radiation data. The data is formatted |
|
88 |
+ as an html document. |
|
89 |
+ Parameters: |
|
90 |
+ deviceUrl - url of radiation monitoring device |
|
91 |
+ HttpRequesttimeout - how long to wait for device |
|
92 |
+ to respond to http request |
|
93 |
+ Returns a string containing the radiation data, or None if |
|
94 |
+ not successful. |
|
95 |
+ """ |
|
96 |
+ content = "" |
|
97 |
+ try: |
|
98 |
+ conn = urllib2.urlopen(deviceUrl, timeout=HttpRequestTimeout) |
|
99 |
+ except Exception, exError: |
|
100 |
+ # If no response is received from the device, then assume that |
|
101 |
+ # the device is down or unavailable over the network. In |
|
102 |
+ # that case set the status of the device to offline. |
|
103 |
+ print "%s: device offline: %s" % \ |
|
104 |
+ (getTimeStamp(), exError) |
|
105 |
+ sendOffLineStatusMsg() |
|
106 |
+ return None |
|
107 |
+ else: |
|
108 |
+ for line in conn: |
|
109 |
+ content += line.strip() |
|
110 |
+ if len(content) == 0: |
|
111 |
+ print "%s: HTTP download failed: null content" % \ |
|
112 |
+ (getTimeStamp()) |
|
113 |
+ return None |
|
114 |
+ del conn |
|
115 |
+ |
|
116 |
+ if debugOption: |
|
117 |
+ print "%s\n" % content # DEBUG |
|
118 |
+ return content |
|
119 |
+##end def |
|
120 |
+ |
|
121 |
+def parseDataString(sData, lsData, dData): |
|
122 |
+ """Parse the radiation data JSON string from the radiation |
|
123 |
+ monitoring device into its component parts. |
|
124 |
+ Parameters: |
|
125 |
+ sData - the string containing the data to be parsed |
|
126 |
+ lsData - a list object to contain the parsed data items |
|
127 |
+ dData - a dictionary object to contain the parsed data items |
|
128 |
+ Returns true if successful, false otherwise. |
|
129 |
+ """ |
|
130 |
+ # Clear data array in preparation for loading reformatted data. |
|
131 |
+ while len(lsData) > 0: |
|
132 |
+ elmt = lsData.pop(0) |
|
133 |
+ |
|
134 |
+ try: |
|
135 |
+ dTmp = json.loads(sData[1:-1]) |
|
136 |
+ sTmp = dTmp['radmon'].encode('ascii', 'ignore') |
|
137 |
+ lsTmp = sTmp.split(',') |
|
138 |
+ lsData.extend(lsTmp) |
|
139 |
+ except Exception, exError: |
|
140 |
+ print "%s parse failed: %s" % (getTimeStamp(), exError) |
|
141 |
+ return False |
|
142 |
+ |
|
143 |
+ # Since the device responded, set the status to online. |
|
144 |
+ lsData.insert(-2, "status=online") |
|
145 |
+ |
|
146 |
+ # Load the parsed data into a dictionary for easy access. |
|
147 |
+ for item in lsData: |
|
148 |
+ if "=" in item: |
|
149 |
+ dData[item.split('=')[0]] = item.split('=')[1] |
|
150 |
+ |
|
151 |
+ if debugOption and 0: |
|
152 |
+ print lsData |
|
153 |
+ print dData |
|
154 |
+ return True |
|
155 |
+##end def |
|
156 |
+ |
|
157 |
+def convertData(lsData, dData): |
|
158 |
+ """Convert individual radiation data items as necessary. |
|
159 |
+ Parameters: |
|
160 |
+ lsData - a list object containing the radiation data |
|
161 |
+ dData - a dictionary object containing the radiation data |
|
162 |
+ Returns true if successful, false otherwise. |
|
163 |
+ """ |
|
164 |
+ result = True |
|
165 |
+ |
|
166 |
+ try: |
|
167 |
+ # Convert UTC from radiation monitoring device to local time. |
|
168 |
+ ts_utc = time.strptime(dData['UTC'], "%H:%M:%S %m/%d/%Y") |
|
169 |
+ local_sec = calendar.timegm(ts_utc) |
|
170 |
+ dData['UTC'] = local_sec |
|
171 |
+ except: |
|
172 |
+ print "%s invalid time: %s" % (getTimeStamp(), utc) |
|
173 |
+ result = False |
|
174 |
+ |
|
175 |
+ # Clear data array in preparation for loading reformatted data. |
|
176 |
+ while len(lsData) > 0: |
|
177 |
+ elmt = lsData.pop(0) |
|
178 |
+ |
|
179 |
+ lsData.append("\"UTC\":\"%s\"" % dData['UTC']) |
|
180 |
+ lsData.append("\"CPS\":\"%s\"" % dData['CPS']) |
|
181 |
+ lsData.append("\"CPM\":\"%s\"" % dData['CPM']) |
|
182 |
+ lsData.append("\"uSvPerHr\":\"%s\"" % dData['uSv/hr']) |
|
183 |
+ lsData.append("\"Mode\":\"%s\"" % dData['Mode'].lower()) |
|
184 |
+ lsData.append("\"status\":\"%s\"" % dData['status']) |
|
185 |
+ |
|
186 |
+ return result |
|
187 |
+##end def |
|
188 |
+ |
|
189 |
+def writeJSONfile(lsData): |
|
190 |
+ """Convert individual weather string data items as necessary. |
|
191 |
+ Parameters: |
|
192 |
+ lsData - a list object containing the data to be written |
|
193 |
+ to the JSON file |
|
194 |
+ Returns true if successful, false otherwise. |
|
195 |
+ """ |
|
196 |
+ # Convert the list object to a string. |
|
197 |
+ sTmp = ','.join(lsData) |
|
198 |
+ |
|
199 |
+ # Apply JSON formatting to the string and write it to a |
|
200 |
+ # file for use by html documents. |
|
201 |
+ strJSON = "[{%s}]\n" % (sTmp) |
|
202 |
+ |
|
203 |
+ try: |
|
204 |
+ fc = open(_OUTPUT_DATA_FILE, "w") |
|
205 |
+ fc.write(strJSON) |
|
206 |
+ fc.close() |
|
207 |
+ except Exception, exError: |
|
208 |
+ print "%s: write to JSON file failed: %s" % \ |
|
209 |
+ (getTimeStamp(), exError) |
|
210 |
+ return False |
|
211 |
+ return True |
|
212 |
+## end def |
|
213 |
+ |
|
214 |
+def updateDatabase(dData): |
|
215 |
+ """ |
|
216 |
+ Updates the rrdtool database by executing an rrdtool system command. |
|
217 |
+ Formats the command using the data extracted from the radiation |
|
218 |
+ monitor response. |
|
219 |
+ Parameters: dData - dictionary object containing data items to be |
|
220 |
+ written to the rr database file |
|
221 |
+ Returns true if successful, false otherwise. |
|
222 |
+ """ |
|
223 |
+ # The RR database stores whole units, so convert uSv to Sv. |
|
224 |
+ Svvalue = float(dData['uSv/hr']) * 1.0E-06 # convert micro-Sieverts to Sieverts |
|
225 |
+ |
|
226 |
+ # Create the rrdtool update command. |
|
227 |
+ strCmd = "rrdtool update %s %s:%s:%s" % \ |
|
228 |
+ (_RRD_FILE, dData['UTC'], dData['CPM'], Svvalue) |
|
229 |
+ if debugOption: |
|
230 |
+ print "%s\n" % strCmd # DEBUG |
|
231 |
+ |
|
232 |
+ # Run the command as a subprocess. |
|
233 |
+ try: |
|
234 |
+ subprocess.check_output(strCmd, shell=True, \ |
|
235 |
+ stderr=subprocess.STDOUT) |
|
236 |
+ except subprocess.CalledProcessError, exError: |
|
237 |
+ print "%s: rrdtool update failed: %s" % \ |
|
238 |
+ (getTimeStamp(), exError.output) |
|
239 |
+ return False |
|
240 |
+ |
|
241 |
+ return True |
|
242 |
+##end def |
|
243 |
+ |
|
244 |
+def createGraph(fileName, dataItem, gTitle, gStart): |
|
245 |
+ """Uses rrdtool to create a graph of specified weather data item. |
|
246 |
+ Parameters: |
|
247 |
+ fileName - name of graph image file |
|
248 |
+ dataItem - data item to be graphed |
|
249 |
+ gTitle - a title for the graph |
|
250 |
+ gStart - beginning time of the data to be graphed |
|
251 |
+ Returns true if successful, false otherwise. |
|
252 |
+ """ |
|
253 |
+ gPath = _TMP_DIRECTORY + '/' + fileName + ".png" |
|
254 |
+ |
|
255 |
+ # Create the rrdtool graph command. |
|
256 |
+ strFmt = ("rrdtool graph %s -a PNG -s %s -w 600 -h 150 " |
|
257 |
+ ## "-l 50 -u 110 -r " |
|
258 |
+ "-v %s -t %s " |
|
259 |
+ "DEF:%s=%s:%s:AVERAGE " |
|
260 |
+ "LINE2:%s\#0400ff:") |
|
261 |
+ strCmd = strFmt % (gPath, gStart, dataItem, gTitle, dataItem, \ |
|
262 |
+ _RRD_FILE, dataItem, dataItem) |
|
263 |
+ if debugOption: |
|
264 |
+ print "%s\n" % strCmd # DEBUG |
|
265 |
+ |
|
266 |
+ # Run the command as a subprocess. |
|
267 |
+ try: |
|
268 |
+ result = subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \ |
|
269 |
+ shell=True) |
|
270 |
+ except subprocess.CalledProcessError, exError: |
|
271 |
+ print "rdtool graph failed: %s" % (exError.output) |
|
272 |
+ return False |
|
273 |
+ |
|
274 |
+ if debugOption: |
|
275 |
+ print "rrdtool graph: %s" % result |
|
276 |
+ |
|
277 |
+ return True |
|
278 |
+##end def |
|
279 |
+ |
|
280 |
+def getCLarguments(): |
|
281 |
+ """Get command line arguments. There are three possible arguments |
|
282 |
+ -d turns on debug mode |
|
283 |
+ -t sets the radiation device query interval |
|
284 |
+ -u sets the url of the radiation monitoring device |
|
285 |
+ Returns nothing. |
|
286 |
+ """ |
|
287 |
+ global debugOption, deviceQueryInterval, deviceURL |
|
288 |
+ |
|
289 |
+ index = 1 |
|
290 |
+ while index < len(sys.argv): |
|
291 |
+ if sys.argv[index] == '-d': |
|
292 |
+ debugOption = True |
|
293 |
+ elif sys.argv[index] == '-t': |
|
294 |
+ try: |
|
295 |
+ deviceQueryInterval = abs(int(sys.argv[index + 1])) |
|
296 |
+ except: |
|
297 |
+ print "invalid polling period" |
|
298 |
+ exit(-1) |
|
299 |
+ index += 1 |
|
300 |
+ elif sys.argv[index] == '-u': |
|
301 |
+ deviceURL = sys.argv[index + 1] |
|
302 |
+ index += 1 |
|
303 |
+ else: |
|
304 |
+ cmd_name = sys.argv[0].split('/') |
|
305 |
+ print "Usage: %s {-v} {-d}" % cmd_name[-1] |
|
306 |
+ exit(-1) |
|
307 |
+ index += 1 |
|
308 |
+##end def |
|
309 |
+ |
|
310 |
+def generateGraphs(): |
|
311 |
+ """Generate graphs for display in html documents. |
|
312 |
+ Parameters: none |
|
313 |
+ Returns nothing. |
|
314 |
+ """ |
|
315 |
+ createGraph('radGraph1', 'CPM', "'CPM - Last 24 Hours'", 'end-1day') |
|
316 |
+ createGraph('radGraph2', 'SvperHr', "'Sv/Hr - Last 24 Hours'", 'end-1day') |
|
317 |
+ createGraph('radGraph3', 'CPM', "'CPM - Last 4 Weeks'", 'end-4weeks') |
|
318 |
+ createGraph('radGraph4', 'SvperHr', "'Sv/Hr - Last 4 Weeks'", 'end-4weeks') |
|
319 |
+ createGraph('radGraph5', 'CPM', "'CPM - Past Year'", 'end-12months') |
|
320 |
+ createGraph('radGraph6', 'SvperHr', "'Sv/Hr - Past Year'", 'end-12months') |
|
321 |
+##end def |
|
322 |
+ |
|
323 |
+def main(): |
|
324 |
+ """Handles timing of events and acts as executive routine managing all other |
|
325 |
+ functions. |
|
326 |
+ Parameters: none |
|
327 |
+ Returns nothing. |
|
328 |
+ """ |
|
329 |
+ lastChartUpdateTime = - 1 # last time charts generated |
|
330 |
+ lastDatabaseUpdateTime = -1 # last time the rrdtool database updated |
|
331 |
+ lastWebDataUpdateTime = -1 # last time output JSON file updated |
|
332 |
+ lastDeviceQueryTime = -1 # last time radiation device queried for data |
|
333 |
+ dData = {} # dictionary object for temporary data storage |
|
334 |
+ lsData = [] # list object for temporary data storage |
|
335 |
+ |
|
336 |
+ ## Get command line arguments. |
|
337 |
+ getCLarguments() |
|
338 |
+ |
|
339 |
+ ## Create www data folder if it does not already exist. |
|
340 |
+ if not os.path.isdir(_TMP_DIRECTORY): |
|
341 |
+ os.makedirs(_TMP_DIRECTORY) |
|
342 |
+ |
|
343 |
+ ## Exit with error if cannot find the rrdtool database file. |
|
344 |
+ if not os.path.exists(_RRD_FILE): |
|
345 |
+ print "cannot find rrdtool database file: terminating" |
|
346 |
+ exit(1) |
|
347 |
+ |
|
348 |
+ ## main loop |
|
349 |
+ while True: |
|
350 |
+ |
|
351 |
+ currentTime = time.time() |
|
352 |
+ |
|
353 |
+ # At the radiation device query interval request and process |
|
354 |
+ # the data from the device. |
|
355 |
+ if currentTime - lastDeviceQueryTime > deviceQueryInterval: |
|
356 |
+ lastDeviceQueryTime = currentTime |
|
357 |
+ result = True |
|
358 |
+ |
|
359 |
+ # Get the data string from the device. |
|
360 |
+ sData = getDataString(deviceUrl, _HTTP_REQUEST_TIMEOUT) |
|
361 |
+ if sData == None: |
|
362 |
+ result = False |
|
363 |
+ |
|
364 |
+ # If successful parse the data. |
|
365 |
+ if result: |
|
366 |
+ result = parseDataString(sData, lsData, dData) |
|
367 |
+ |
|
368 |
+ # If parsing successful, convert the data. |
|
369 |
+ if result: |
|
370 |
+ result = convertData(lsData, dData) |
|
371 |
+ |
|
372 |
+ # At the web update interval, update the JSON file used to pass |
|
373 |
+ # radiation data to html documents. |
|
374 |
+ if currentTime - lastWebDataUpdateTime > _WEB_DATA_UPDATE_INTERVAL: |
|
375 |
+ lastWebDataUpdateTime = currentTime |
|
376 |
+ if result: |
|
377 |
+ lsData[0] = "\"time\":\"%s\"" % getTimeStamp() |
|
378 |
+ writeJSONfile(lsData) |
|
379 |
+ |
|
380 |
+ # At the rrdtool database update interval, update the database. |
|
381 |
+ if currentTime - lastDatabaseUpdateTime > _DATABASE_UPDATE_INTERVAL: |
|
382 |
+ if result: |
|
383 |
+ lastDatabaseUpdateTime = currentTime |
|
384 |
+ ## Update the round robin database with the parsed data. |
|
385 |
+ result = updateDatabase(dData) |
|
386 |
+ |
|
387 |
+ # At the chart generation interval, generate charts. |
|
388 |
+ if currentTime - lastChartUpdateTime > _CHART_UPDATE_INTERVAL: |
|
389 |
+ lastChartUpdateTime = currentTime |
|
390 |
+ p = multiprocessing.Process(target=generateGraphs, args=()) |
|
391 |
+ p.start() |
|
392 |
+ |
|
393 |
+ # Relinquish processing back to the operating system until |
|
394 |
+ # the next update interval. |
|
395 |
+ |
|
396 |
+ elapsedTime = time.time() - currentTime |
|
397 |
+ if debugOption: |
|
398 |
+ print "processing time: %s\n" % elapsedTime |
|
399 |
+ remainingTime = _WEB_DATA_UPDATE_INTERVAL - elapsedTime |
|
400 |
+ if remainingTime > 0: |
|
401 |
+ time.sleep(remainingTime) |
|
402 |
+ |
|
403 |
+ ## end while |
|
404 |
+## end def |
|
405 |
+ |
|
406 |
+if __name__ == '__main__': |
|
407 |
+ main() |
|
408 |
+ |