... | ... |
@@ -2,7 +2,7 @@ |
2 | 2 |
<body> |
3 | 3 |
<h4>Background Radiation Monitoring Device</h4> |
4 | 4 |
|
5 |
-<p>Building a simple background radiation monitor provides an excellent introduction to "Internet of Things" ideas and concepts. This project site contains all you need to know in order to build your own background radiation monitor - a Geiger counter "Internet Thing" - for under $150 in parts.</p> |
|
5 |
+<p>Building a simple background radiation monitor provides an excellent introduction to "Web of Things" ideas and concepts. This project site contains all you need to know in order to build your own background radiation monitor - a Geiger counter "Internet Thing" - for under $150 in parts.</p> |
|
6 | 6 |
|
7 | 7 |
<p>Building the background radiation monitor will give you an excellent introduction to a variety of technologies. You will learn about programming Arduino micro-controllers and assembling electronic components. You will learn about Linux server software, scripting for Internet applications, and displaying information with a web page.</p> |
8 | 8 |
|
... | ... |
@@ -10,7 +10,7 @@ |
10 | 10 |
|
11 | 11 |
<p>This project encompasses two software components. One component runs on the Arduino Uno with attached Ethernet shield. The other component runs on a Linux web server. Besides the Arduino Uno with attached Ethernet shield, the other required component is a modified Mighty Ohm Geiger counter.</p> |
12 | 12 |
|
13 |
-<img src="docs/RadiationMonitor.jpg"><br> |
|
13 |
+<img src="MtyOhmGeigerCounter.jpg"><br> |
|
14 | 14 |
<b>Radiation monitor electronics assembly mounted in box.</b> |
15 | 15 |
<br> |
16 | 16 |
</body> |
17 | 17 |
old mode 100644 |
18 | 18 |
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__': |