... | ... |
@@ -1,8 +1,8 @@ |
1 | 1 |
<html> |
2 | 2 |
<body> |
3 |
-<h4>Ardensig - Monitor your Node's Performance</h4> |
|
3 |
+<h4>Arednsig - Monitor your Node's Performance</h4> |
|
4 | 4 |
<p> |
5 |
-Ardensig enables you to view your node's received signal strength and signal to noise ratio for any time period stored in the database. Ardensig uses a "round-robin" database to store up to years worth of data. Once you install Arednsig it begins keeping a database. You can use the web browser to create charts of any time period after installing Arednsig. |
|
5 |
+Arednsig enables you to view your node's received signal strength and signal to noise ratio for any time period stored in the database. Arednsig uses a "round-robin" database to store up to years worth of data. Once you install Arednsig it begins keeping a database. You can use the web browser to create charts of any time period after installing Arednsig. |
|
6 | 6 |
</p> |
7 | 7 |
<p> |
8 | 8 |
Installing and using Arednsig will give you an excellent introduction to Linux "devops". You will learn about Linux server software systems and architecture, scripting for Internet applications, and displaying information in a web page. |
... | ... |
@@ -11,5 +11,13 @@ Installing and using Arednsig will give you an excellent introduction to Linux " |
11 | 11 |
You can find all the required software available under open source, GNU license on this project site. Please read the |
12 | 12 |
<a href="docs/Arednsig-Installation.pdf">installation instructions</a> for details on how to install Arednsig. |
13 | 13 |
</p> |
14 |
+<p> |
|
15 |
+Currently two versions of the Aredn firmware are supported by the Arednsig application |
|
16 |
+<ul> |
|
17 |
+<li>3.19.3.0</li> |
|
18 |
+<li>3.20.3.0</li> |
|
19 |
+</ul> |
|
20 |
+Please download the software from the appropriate folder. |
|
21 |
+</p> |
|
14 | 22 |
</body> |
15 | 23 |
</html> |
16 | 24 |
deleted file mode 100755 |
... | ... |
@@ -1,27 +0,0 @@ |
1 |
-#!/bin/bash |
|
2 |
-# |
|
3 |
-# The monitor url can look something like http://192.168.1.155, or |
|
4 |
-# something linke http://radiationMonitor.domain.com depending on |
|
5 |
-# whether your local network uses a domain name server. |
|
6 |
-# |
|
7 |
- |
|
8 |
-APP_PATH="/home/$USER/bin" |
|
9 |
-LOG_PATH="/home/$USER/log" |
|
10 |
- |
|
11 |
-AGENT_NAME="[a]rednsigAgent.py" |
|
12 |
-NODE_URL="http://localnode:8080/cgi-bin/signal.json" |
|
13 |
- |
|
14 |
-POLLING_INTERVAL="60" |
|
15 |
- |
|
16 |
-PROCESS_ID="$(ps x | awk -v a=$AGENT_NAME '$7 ~ a {print $1}')" |
|
17 |
- |
|
18 |
-if [ -n "$PROCESS_ID" ]; then |
|
19 |
- if [ "$1" != "-q" ]; then |
|
20 |
- printf "arednsig agent running [%s]\n" $PROCESS_ID |
|
21 |
- fi |
|
22 |
-else |
|
23 |
- printf "starting up arednsig agent\n" |
|
24 |
- cd $APP_PATH |
|
25 |
- $(./$AGENT_NAME -u $NODE_URL -p $POLLING_INTERVAL >> \ |
|
26 |
- $LOG_PATH/arednsigAgent.log 2>&1 &) |
|
27 |
-fi |
28 | 0 |
deleted file mode 100755 |
... | ... |
@@ -1,14 +0,0 @@ |
1 |
-#!/bin/bash |
|
2 |
-# Stop the radmon agent process and clean up environment. |
|
3 |
- |
|
4 |
- |
|
5 |
-AGENT_NAME="[a]rednsigAgent.py" |
|
6 |
- |
|
7 |
-PROCESS_ID="$(ps x | awk -v a=$AGENT_NAME '$7 ~ a {print $1}')" |
|
8 |
- |
|
9 |
-if [ -n "$PROCESS_ID" ]; then |
|
10 |
- printf "killing arednsig agent [%s]\n" $PROCESS_ID |
|
11 |
- kill $PROCESS_ID |
|
12 |
-else |
|
13 |
- echo arednsig agent not running |
|
14 |
-fi |
15 | 0 |
deleted file mode 100755 |
... | ... |
@@ -1,704 +0,0 @@ |
1 |
-#!/usr/bin/python -u |
|
2 |
-# The -u option above turns off block buffering of python output. This |
|
3 |
-# assures that each error message gets individually printed to the log file. |
|
4 |
-# |
|
5 |
-# Module: arednsigAgent.py |
|
6 |
-# |
|
7 |
-# Description: This module acts as an agent between the aredn node |
|
8 |
-# and aredn mest services. The agent periodically sends an http |
|
9 |
-# request to the aredn node, processes the response from |
|
10 |
-# the node, and performs a number of operations: |
|
11 |
-# - conversion of data items |
|
12 |
-# - update a round robin (rrdtool) database with the node data |
|
13 |
-# - periodically generate graphic charts for display in html documents |
|
14 |
-# - write the processed node status to a JSON file for use by html |
|
15 |
-# documents |
|
16 |
-# |
|
17 |
-# Copyright 2020 Jeff Owrey |
|
18 |
-# This program is free software: you can redistribute it and/or modify |
|
19 |
-# it under the terms of the GNU General Public License as published by |
|
20 |
-# the Free Software Foundation, either version 3 of the License, or |
|
21 |
-# (at your option) any later version. |
|
22 |
-# |
|
23 |
-# This program is distributed in the hope that it will be useful, |
|
24 |
-# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
25 |
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
26 |
-# GNU General Public License for more details. |
|
27 |
-# |
|
28 |
-# You should have received a copy of the GNU General Public License |
|
29 |
-# along with this program. If not, see http://www.gnu.org/license. |
|
30 |
-# |
|
31 |
-# Revision History |
|
32 |
-# * v20 released 11 Jan 2020 by J L Owrey; first release |
|
33 |
-# * v21 released 13 Feb 2020 by J L Owrey; fixed bug occuring when node |
|
34 |
-# powers on and signal data memory is empty. Data points with N/A data |
|
35 |
-# are discarded. |
|
36 |
-# |
|
37 |
-#2345678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
38 |
- |
|
39 |
-import os |
|
40 |
-import urllib2 |
|
41 |
-import sys |
|
42 |
-import signal |
|
43 |
-import subprocess |
|
44 |
-import multiprocessing |
|
45 |
-import time |
|
46 |
-import json |
|
47 |
- |
|
48 |
-_USER = os.environ['USER'] |
|
49 |
-_HOSTNAME = os.uname()[1] |
|
50 |
- |
|
51 |
- ### DEFAULT AREDN NODE URL ### |
|
52 |
- |
|
53 |
-# set url of the aredn node |
|
54 |
- |
|
55 |
-_DEFAULT_AREDN_NODE_URL = "http://localnode:8080/cgi-bin/signal.json" |
|
56 |
- |
|
57 |
-if _HOSTNAME == "raspi2": |
|
58 |
- _DEFAULT_AREDN_NODE_URL = "http://192.168.1.30:8080/cgi-bin/signal.json" |
|
59 |
- |
|
60 |
- ### FILE AND FOLDER LOCATIONS ### |
|
61 |
- |
|
62 |
-# folder for containing dynamic data objects |
|
63 |
-_DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER |
|
64 |
-# folder for charts and output data file |
|
65 |
-_CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/" |
|
66 |
-# location of data output file |
|
67 |
-_OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js" |
|
68 |
-# dummy output data file |
|
69 |
-_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js" |
|
70 |
-# database that stores node data |
|
71 |
-_RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER |
|
72 |
- |
|
73 |
- ### GLOBAL CONSTANTS ### |
|
74 |
- |
|
75 |
-# max number of failed data requests allowed |
|
76 |
-_MAX_FAILED_DATA_REQUESTS = 0 |
|
77 |
-# interval in minutes between data requests to the aredn node |
|
78 |
-_DEFAULT_DATA_REQUEST_INTERVAL = 60 |
|
79 |
-# number seconds to wait for a response to HTTP request |
|
80 |
-_HTTP_REQUEST_TIMEOUT = 10 |
|
81 |
-# standard chart width in pixels |
|
82 |
-_CHART_WIDTH = 600 |
|
83 |
-# standard chart height in pixels |
|
84 |
-_CHART_HEIGHT = 150 |
|
85 |
-# Set this to True only if this server is intended to relay raw |
|
86 |
-# node data to a mirror server. |
|
87 |
-_RELAY_SERVER = False |
|
88 |
- |
|
89 |
- ### GLOBAL VARIABLES ### |
|
90 |
- |
|
91 |
-# turn on or off of verbose debugging information |
|
92 |
-debugOption = False |
|
93 |
-verboseDebug = False |
|
94 |
- |
|
95 |
-# The following two items are used for detecting system faults |
|
96 |
-# and aredn node online or offline status. |
|
97 |
- |
|
98 |
-# count of failed attempts to get data from aredn node |
|
99 |
-failedUpdateCount = 0 |
|
100 |
-# detected status of aredn node device |
|
101 |
-nodeOnline = True |
|
102 |
- |
|
103 |
-# ip address of aredn node |
|
104 |
-arednNodeUrl = _DEFAULT_AREDN_NODE_URL |
|
105 |
-# frequency of data requests to aredn node |
|
106 |
-dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL |
|
107 |
-# last node request time |
|
108 |
-lastDataPointTime = -1 |
|
109 |
- |
|
110 |
- ### PRIVATE METHODS ### |
|
111 |
- |
|
112 |
-def getTimeStamp(): |
|
113 |
- """ |
|
114 |
- Set the error message time stamp to the local system time. |
|
115 |
- Parameters: none |
|
116 |
- Returns: string containing the time stamp |
|
117 |
- """ |
|
118 |
- return time.strftime( "%m/%d/%Y %T", time.localtime() ) |
|
119 |
-##end def |
|
120 |
- |
|
121 |
-def getLastDataPointTime(): |
|
122 |
- """ |
|
123 |
- Get the timestamp of the most recent update to the round robin |
|
124 |
- database. |
|
125 |
- Parameters: none |
|
126 |
- Returns: string epoch time stamp of the last rrd update |
|
127 |
- """ |
|
128 |
- strCmd = "rrdtool lastupdate %s" % \ |
|
129 |
- (_RRD_FILE) |
|
130 |
- |
|
131 |
- # Run the command as a subprocess. |
|
132 |
- try: |
|
133 |
- result = subprocess.check_output(strCmd, shell=True, \ |
|
134 |
- stderr=subprocess.STDOUT) |
|
135 |
- except subprocess.CalledProcessError, exError: |
|
136 |
- print "%s: rrdtool update failed: %s" % \ |
|
137 |
- (getTimeStamp(), exError.output) |
|
138 |
- return None |
|
139 |
- |
|
140 |
- # Return just the epoch time stamp of the last rrd update |
|
141 |
- return int((result.split('\n')[-2]).split(':')[0]) |
|
142 |
-##end def |
|
143 |
- |
|
144 |
-def setStatusToOffline(): |
|
145 |
- """Set the detected status of the aredn node to |
|
146 |
- "offline" and inform downstream clients by removing input |
|
147 |
- and output data files. |
|
148 |
- Parameters: none |
|
149 |
- Returns: nothing |
|
150 |
- """ |
|
151 |
- global nodeOnline |
|
152 |
- |
|
153 |
- # Inform downstream clients by removing output data file. |
|
154 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
155 |
- os.remove(_OUTPUT_DATA_FILE) |
|
156 |
- if os.path.exists(_DUMMY_OUTPUT_FILE): |
|
157 |
- os.remove(_DUMMY_OUTPUT_FILE) |
|
158 |
- # If the aredn node was previously online, then send |
|
159 |
- # a message that we are now offline. |
|
160 |
- if nodeOnline: |
|
161 |
- print '%s aredn node offline' % getTimeStamp() |
|
162 |
- nodeOnline = False |
|
163 |
-##end def |
|
164 |
- |
|
165 |
-def terminateAgentProcess(signal, frame): |
|
166 |
- """Send a message to log when the agent process gets killed |
|
167 |
- by the operating system. Inform downstream clients |
|
168 |
- by removing input and output data files. |
|
169 |
- Parameters: |
|
170 |
- signal, frame - dummy parameters |
|
171 |
- Returns: nothing |
|
172 |
- """ |
|
173 |
- # Inform downstream clients by removing output data file. |
|
174 |
- if os.path.exists(_OUTPUT_DATA_FILE): |
|
175 |
- os.remove(_OUTPUT_DATA_FILE) |
|
176 |
- if os.path.exists(_DUMMY_OUTPUT_FILE): |
|
177 |
- os.remove(_DUMMY_OUTPUT_FILE) |
|
178 |
- print '%s terminating arednsig agent process' % \ |
|
179 |
- (getTimeStamp()) |
|
180 |
- sys.exit(0) |
|
181 |
-##end def |
|
182 |
- |
|
183 |
- ### PUBLIC METHODS ### |
|
184 |
- |
|
185 |
-def getArednNodeData(): |
|
186 |
- """Send http request to aredn node. The response from the |
|
187 |
- node contains the node signal data as unformatted ascii text. |
|
188 |
- Parameters: none |
|
189 |
- Returns: a string containing the node signal data if successful, |
|
190 |
- or None if not successful |
|
191 |
- """ |
|
192 |
- try: |
|
193 |
- conn = urllib2.urlopen(arednNodeUrl, timeout=_HTTP_REQUEST_TIMEOUT) |
|
194 |
- |
|
195 |
- # Format received data into a single string. |
|
196 |
- content = "" |
|
197 |
- for line in conn: |
|
198 |
- content += line.strip() |
|
199 |
- del conn |
|
200 |
- |
|
201 |
- except Exception, exError: |
|
202 |
- # If no response is received from the device, then assume that |
|
203 |
- # the device is down or unavailable over the network. In |
|
204 |
- # that case return None to the calling function. |
|
205 |
- print "%s http error: %s" % (getTimeStamp(), exError) |
|
206 |
- return None |
|
207 |
- |
|
208 |
- if verboseDebug: |
|
209 |
- print "http request successful: %d bytes" % len(content) |
|
210 |
- |
|
211 |
- return content |
|
212 |
-##end def |
|
213 |
- |
|
214 |
-def parseNodeData(sData, ldData): |
|
215 |
- """Parse the node signal data JSON string from the aredn node |
|
216 |
- into its component parts. |
|
217 |
- Parameters: |
|
218 |
- sData - the string containing the data to be parsed |
|
219 |
- dData - a dictionary object to contain the parsed data items |
|
220 |
- Returns: True if successful, False otherwise |
|
221 |
- """ |
|
222 |
- iTrail = int(dataRequestInterval) |
|
223 |
- |
|
224 |
- try: |
|
225 |
- ldTmp = json.loads(sData[1:-1]) |
|
226 |
- ldTmp = ldTmp[-iTrail:] |
|
227 |
- if len(ldTmp) != iTrail: |
|
228 |
- #raise Exception("truncated list") |
|
229 |
- pass |
|
230 |
- except Exception, exError: |
|
231 |
- print "%s parse failed: %s" % (getTimeStamp(), exError) |
|
232 |
- return False |
|
233 |
- |
|
234 |
- del ldData[:] |
|
235 |
- for item in ldTmp: |
|
236 |
- ldData.append(item) |
|
237 |
- |
|
238 |
- if verboseDebug: |
|
239 |
- print "parse successful: %d data points" % len(ldData) |
|
240 |
- return True |
|
241 |
-##end def |
|
242 |
- |
|
243 |
-def convertData(ldData): |
|
244 |
- """Convert individual node signal data items as necessary. |
|
245 |
- Parameters: |
|
246 |
- dData - a dictionary object containing the node signal data |
|
247 |
- Returns: True if successful, False otherwise |
|
248 |
- """ |
|
249 |
- # parse example string |
|
250 |
- # {u'tx_mcs': u'15', u'rx_mcs': u'15', u'm': 47, |
|
251 |
- # u'label': u'01/10/2020 22:17:01', u'rx_rate': u'130', |
|
252 |
- # u'y': [-48, -95], u'x': 1578694621000, u'tx_rate': u'130'} |
|
253 |
- # |
|
254 |
- index = 0 |
|
255 |
- while index < len(ldData): |
|
256 |
- item = ldData[index] |
|
257 |
- try: |
|
258 |
- item['time'] = int(item.pop('x')) / 1000 |
|
259 |
- item['signal'] = int(item['y'][0]) |
|
260 |
- item['noise'] = int(item['y'][1]) |
|
261 |
- item['snr'] = int(item.pop('m')) |
|
262 |
- item['rx_mcs'] = int(item.pop('rx_mcs')) |
|
263 |
- item['tx_mcs'] = int(item.pop('tx_mcs')) |
|
264 |
- item['rx_rate'] = int(item.pop('rx_rate')) |
|
265 |
- item['tx_rate'] = int(item.pop('tx_rate')) |
|
266 |
- item.pop('y') |
|
267 |
- item.pop('label') |
|
268 |
- except Exception, exError: |
|
269 |
- print "%s convert data item failed: %s" % (getTimeStamp(), exError) |
|
270 |
- print "discarding %s" % item |
|
271 |
- #return False |
|
272 |
- ldData.pop(index) |
|
273 |
- else: |
|
274 |
- index += 1 |
|
275 |
- ##end for |
|
276 |
- |
|
277 |
- if len(ldData) > 0: |
|
278 |
- if verboseDebug: |
|
279 |
- print "convert data successful" |
|
280 |
- return True |
|
281 |
- else: |
|
282 |
- print "convert data failed" |
|
283 |
- return False |
|
284 |
-##end def |
|
285 |
- |
|
286 |
-def updateDatabase(ldData): |
|
287 |
- """ |
|
288 |
- Update the rrdtool database by executing an rrdtool system command. |
|
289 |
- Format the command using the data extracted from the aredn node |
|
290 |
- response. |
|
291 |
- Parameters: dData - dictionary object containing data items to be |
|
292 |
- written to the rr database file |
|
293 |
- Returns: True if successful, False otherwise |
|
294 |
- """ |
|
295 |
- updateCount = 0 |
|
296 |
- lastDataPointTime = getLastDataPointTime() |
|
297 |
- |
|
298 |
- if verboseDebug: |
|
299 |
- print "updating database..." |
|
300 |
- |
|
301 |
- for item in ldData: |
|
302 |
- |
|
303 |
- if item['time'] <= lastDataPointTime: |
|
304 |
- if verboseDebug: |
|
305 |
- print "%s invalid timestamp: discarding data" % \ |
|
306 |
- (getTimeStamp()) |
|
307 |
- continue |
|
308 |
- |
|
309 |
- # Format the rrdtool update command. |
|
310 |
- strFmt = "rrdtool update %s %s:%s:%s:%s:%s:%s:%s:%s" |
|
311 |
- strCmd = strFmt % (_RRD_FILE, item['time'], item['signal'], \ |
|
312 |
- item['noise'], item['snr'], item['rx_mcs'], \ |
|
313 |
- item['tx_mcs'], item['rx_rate'], item['tx_rate']) |
|
314 |
- |
|
315 |
- if verboseDebug: |
|
316 |
- print "%s" % strCmd # DEBUG |
|
317 |
- |
|
318 |
- # Run the command as a subprocess. |
|
319 |
- try: |
|
320 |
- subprocess.check_output(strCmd, shell=True, \ |
|
321 |
- stderr=subprocess.STDOUT) |
|
322 |
- except subprocess.CalledProcessError, exError: |
|
323 |
- print "%s: rrdtool update failed: %s" % \ |
|
324 |
- (getTimeStamp(), exError.output) |
|
325 |
- return False |
|
326 |
- updateCount += 1 |
|
327 |
- ##end for |
|
328 |
- |
|
329 |
- if debugOption: |
|
330 |
- print '%s added %d data points to database' % \ |
|
331 |
- (getTimeStamp(), updateCount) |
|
332 |
- return True |
|
333 |
-##end def |
|
334 |
- |
|
335 |
-def writeOutputDataFile(sData, ldData): |
|
336 |
- """Write node data items to the output data file, formatted as |
|
337 |
- a Javascript file. This file may then be accessed and used by |
|
338 |
- by downstream clients, for instance, in HTML documents. |
|
339 |
- Parameters: |
|
340 |
- sData - a string object containing the data to be written |
|
341 |
- to the output data file |
|
342 |
- Returns: True if successful, False otherwise |
|
343 |
- """ |
|
344 |
- # Write file for use by html clients. The following two |
|
345 |
- # data items are sent to the client file. |
|
346 |
- # * The last database update date and time |
|
347 |
- # * The data request interval |
|
348 |
- lastUpdate = time.strftime( "%m.%d.%Y %T", |
|
349 |
- time.localtime(ldData[-1]['time']) ) |
|
350 |
- sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \ |
|
351 |
- (lastUpdate, dataRequestInterval) |
|
352 |
- try: |
|
353 |
- fc = open(_DUMMY_OUTPUT_FILE, "w") |
|
354 |
- fc.write(sDate) |
|
355 |
- fc.close() |
|
356 |
- except Exception, exError: |
|
357 |
- print "%s write node file failed: %s" % (getTimeStamp(), exError) |
|
358 |
- return False |
|
359 |
- |
|
360 |
- if _RELAY_SERVER: |
|
361 |
- # Write the entire node data response to the output data file. |
|
362 |
- try: |
|
363 |
- fc = open(_OUTPUT_DATA_FILE, "w") |
|
364 |
- fc.write(sData) |
|
365 |
- fc.close() |
|
366 |
- except Exception, exError: |
|
367 |
- print "%s write output file failed: %s" % \ |
|
368 |
- (getTimeStamp(), exError) |
|
369 |
- return False |
|
370 |
- if verboseDebug: |
|
371 |
- print "write output data file: %d bytes" % len(sData) |
|
372 |
- |
|
373 |
- return True |
|
374 |
-## end def |
|
375 |
- |
|
376 |
-def setNodeStatus(updateSuccess): |
|
377 |
- """Detect if aredn node is offline or not available on |
|
378 |
- the network. After a set number of attempts to get data |
|
379 |
- from the node set a flag that the node is offline. |
|
380 |
- Parameters: |
|
381 |
- updateSuccess - a boolean that is True if data request |
|
382 |
- successful, False otherwise |
|
383 |
- Returns: nothing |
|
384 |
- """ |
|
385 |
- global failedUpdateCount, nodeOnline |
|
386 |
- |
|
387 |
- if updateSuccess: |
|
388 |
- failedUpdateCount = 0 |
|
389 |
- # Set status and send a message to the log if the node was |
|
390 |
- # previously offline and is now online. |
|
391 |
- if not nodeOnline: |
|
392 |
- print '%s aredn node online' % getTimeStamp() |
|
393 |
- nodeOnline = True |
|
394 |
- else: |
|
395 |
- # The last attempt failed, so update the failed attempts |
|
396 |
- # count. |
|
397 |
- failedUpdateCount += 1 |
|
398 |
- |
|
399 |
- if failedUpdateCount > _MAX_FAILED_DATA_REQUESTS: |
|
400 |
- # Max number of failed data requests, so set |
|
401 |
- # node status to offline. |
|
402 |
- setStatusToOffline() |
|
403 |
-##end def |
|
404 |
- |
|
405 |
-def createGraph(fileName, dataItem, gLabel, gTitle, gStart, |
|
406 |
- lower, upper, addTrend, autoScale): |
|
407 |
- """Uses rrdtool to create a graph of specified node data item. |
|
408 |
- Parameters: |
|
409 |
- fileName - name of file containing the graph |
|
410 |
- dataItem - data item to be graphed |
|
411 |
- gLabel - string containing a graph label for the data item |
|
412 |
- gTitle - string containing a title for the graph |
|
413 |
- gStart - beginning time of the graphed data |
|
414 |
- lower - lower bound for graph ordinate #NOT USED |
|
415 |
- upper - upper bound for graph ordinate #NOT USED |
|
416 |
- addTrend - 0, show only graph data |
|
417 |
- 1, show only a trend line |
|
418 |
- 2, show a trend line and the graph data |
|
419 |
- autoScale - if True, then use vertical axis auto scaling |
|
420 |
- (lower and upper parameters are ignored), otherwise use |
|
421 |
- lower and upper parameters to set vertical axis scale |
|
422 |
- Returns: True if successful, False otherwise |
|
423 |
- """ |
|
424 |
- gPath = _CHARTS_DIRECTORY + fileName + ".png" |
|
425 |
- trendWindow = { 'end-1day': 7200, |
|
426 |
- 'end-4weeks': 172800, |
|
427 |
- 'end-12months': 604800 } |
|
428 |
- |
|
429 |
- # Format the rrdtool graph command. |
|
430 |
- |
|
431 |
- # Set chart start time, height, and width. |
|
432 |
- strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \ |
|
433 |
- % (gPath, gStart, _CHART_WIDTH, _CHART_HEIGHT) |
|
434 |
- |
|
435 |
- # Set the range and scaling of the chart y-axis. |
|
436 |
- if lower < upper: |
|
437 |
- strCmd += "-l %s -u %s -r " % (lower, upper) |
|
438 |
- elif autoScale: |
|
439 |
- strCmd += "-A " |
|
440 |
- strCmd += "-Y " |
|
441 |
- |
|
442 |
- # Set the chart ordinate label and chart title. |
|
443 |
- strCmd += "-v %s -t %s " % (gLabel, gTitle) |
|
444 |
- |
|
445 |
- # Show the data, or a moving average trend line over |
|
446 |
- # the data, or both. |
|
447 |
- strCmd += "DEF:dSeries=%s:%s:LAST " % (_RRD_FILE, dataItem) |
|
448 |
- if addTrend == 0: |
|
449 |
- strCmd += "LINE1:dSeries#0400ff " |
|
450 |
- elif addTrend == 1: |
|
451 |
- strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
452 |
- % trendWindow[gStart] |
|
453 |
- elif addTrend == 2: |
|
454 |
- strCmd += "LINE1:dSeries#0400ff " |
|
455 |
- strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 " \ |
|
456 |
- % trendWindow[gStart] |
|
457 |
- |
|
458 |
- if verboseDebug: |
|
459 |
- print "%s" % strCmd # DEBUG |
|
460 |
- |
|
461 |
- # Run the formatted rrdtool command as a subprocess. |
|
462 |
- try: |
|
463 |
- result = subprocess.check_output(strCmd, \ |
|
464 |
- stderr=subprocess.STDOUT, \ |
|
465 |
- shell=True) |
|
466 |
- except subprocess.CalledProcessError, exError: |
|
467 |
- print "rrdtool graph failed: %s" % (exError.output) |
|
468 |
- return False |
|
469 |
- |
|
470 |
- if debugOption: |
|
471 |
- print "rrdtool graph: %s\n" % result, |
|
472 |
- return True |
|
473 |
- |
|
474 |
-##end def |
|
475 |
- |
|
476 |
-def generateGraphs(): |
|
477 |
- """Generate graphs for display in html documents. |
|
478 |
- Parameters: none |
|
479 |
- Returns: nothing |
|
480 |
- """ |
|
481 |
- autoScale = False |
|
482 |
- |
|
483 |
- # The following will force creation of charts |
|
484 |
- # of only signal strength and S/N charts. Note that the following |
|
485 |
- # data items appear constant and do not show variation with time: |
|
486 |
- # noise level, rx mcs, rx rate, tx mcs, tx rate. Therefore, until |
|
487 |
- # these parameters are demonstrated to vary in time, there is no point |
|
488 |
- # in creating the charts for these data items. |
|
489 |
- createAllCharts = False |
|
490 |
- |
|
491 |
- # 24 hour stock charts |
|
492 |
- |
|
493 |
- createGraph('24hr_signal', 'S', 'dBm', |
|
494 |
- 'RSSI\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
|
495 |
- createGraph('24hr_snr', 'SNR', 'dB', |
|
496 |
- 'SNR\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, autoScale) |
|
497 |
- |
|
498 |
- if createAllCharts: |
|
499 |
- createGraph('24hr_noise', 'N', 'dBm', |
|
500 |
- 'Noise\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, |
|
501 |
- autoScale) |
|
502 |
- createGraph('24hr_rx_rate', 'RX_RATE', 'Mbps', |
|
503 |
- 'Rx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, |
|
504 |
- autoScale) |
|
505 |
- createGraph('24hr_tx_rate', 'TX_RATE', 'Mbps', |
|
506 |
- 'Tx\ Rate\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, |
|
507 |
- autoScale) |
|
508 |
- createGraph('24hr_rx_mcs', 'RX_MCS', 'Index', |
|
509 |
- 'Rx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, |
|
510 |
- autoScale) |
|
511 |
- createGraph('24hr_tx_mcs', 'TX_MCS', 'Index', |
|
512 |
- 'Tx\ MCS\ -\ Last\ 24\ Hours', 'end-1day', 0, 0, 2, |
|
513 |
- autoScale) |
|
514 |
- |
|
515 |
- # 4 week stock charts |
|
516 |
- |
|
517 |
- createGraph('4wk_signal', 'S', 'dBm', |
|
518 |
- 'RSSI\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
|
519 |
- createGraph('4wk_snr', 'SNR', 'dB', |
|
520 |
- 'SNR\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, autoScale) |
|
521 |
- |
|
522 |
- if createAllCharts: |
|
523 |
- createGraph('4wk_noise', 'N', 'dBm', |
|
524 |
- 'Noise\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, |
|
525 |
- autoScale) |
|
526 |
- createGraph('4wk_rx_rate', 'RX_RATE', 'Mbps', |
|
527 |
- 'Rx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, |
|
528 |
- autoScale) |
|
529 |
- createGraph('4wk_tx_rate', 'TX_RATE', 'Mbps', |
|
530 |
- 'Tx\ Rate\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, |
|
531 |
- autoScale) |
|
532 |
- createGraph('4wk_rx_mcs', 'RX_MCS', 'Index', |
|
533 |
- 'Rx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, |
|
534 |
- autoScale) |
|
535 |
- createGraph('4wk_tx_mcs', 'TX_MCS', 'Index', |
|
536 |
- 'Tx\ MCS\ -\ Last\ 4\ Weeks', 'end-4weeks', 0, 0, 2, |
|
537 |
- autoScale) |
|
538 |
- |
|
539 |
- # 12 month stock charts |
|
540 |
- |
|
541 |
- createGraph('12m_signal', 'S', 'dBm', |
|
542 |
- 'RSSI\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
|
543 |
- createGraph('12m_snr', 'SNR', 'dB', |
|
544 |
- 'SNR\ -\ Past\ Year', 'end-12months', 0, 0, 2, autoScale) |
|
545 |
- |
|
546 |
- if createAllCharts: |
|
547 |
- createGraph('12m_noise', 'N', 'dBm', |
|
548 |
- 'Noise\ -\ Past\ Year', 'end-12months', 0, 0, 2, |
|
549 |
- autoScale) |
|
550 |
- createGraph('12m_rx_rate', 'RX_RATE', 'Mbps', |
|
551 |
- 'Rx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, |
|
552 |
- autoScale) |
|
553 |
- createGraph('12m_tx_rate', 'TX_RATE', 'Mbps', |
|
554 |
- 'Tx\ Rate\ -\ Past\ Year', 'end-12months', 0, 0, 2, |
|
555 |
- autoScale) |
|
556 |
- createGraph('12m_rx_mcs', 'RX_MCS', 'Index', |
|
557 |
- 'Rx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, |
|
558 |
- autoScale) |
|
559 |
- createGraph('12m_tx_mcs', 'TX_MCS', 'Index', |
|
560 |
- 'Tx\ MCS\ -\ Past\ Year', 'end-12months', 0, 0, 2, |
|
561 |
- autoScale) |
|
562 |
- if debugOption: |
|
563 |
- #print # print a blank line to improve readability when in debug mode |
|
564 |
- pass |
|
565 |
-##end def |
|
566 |
- |
|
567 |
-def getCLarguments(): |
|
568 |
- """Get command line arguments. There are four possible arguments |
|
569 |
- -d turns on debug mode |
|
570 |
- -v turns on verbose debug mode |
|
571 |
- -t sets the aredn node query interval |
|
572 |
- -u sets the url of the aredn nodeing device |
|
573 |
- Returns: nothing |
|
574 |
- """ |
|
575 |
- global debugOption, verboseDebug, dataRequestInterval, \ |
|
576 |
- arednNodeUrl |
|
577 |
- |
|
578 |
- index = 1 |
|
579 |
- while index < len(sys.argv): |
|
580 |
- if sys.argv[index] == '-d': |
|
581 |
- debugOption = True |
|
582 |
- elif sys.argv[index] == '-v': |
|
583 |
- debugOption = True |
|
584 |
- verboseDebug = True |
|
585 |
- elif sys.argv[index] == '-p': |
|
586 |
- try: |
|
587 |
- dataRequestInterval = abs(int(sys.argv[index + 1])) |
|
588 |
- except: |
|
589 |
- print "invalid polling period" |
|
590 |
- exit(-1) |
|
591 |
- index += 1 |
|
592 |
- elif sys.argv[index] == '-u': |
|
593 |
- arednNodeUrl = sys.argv[index + 1] |
|
594 |
- index += 1 |
|
595 |
- else: |
|
596 |
- cmd_name = sys.argv[0].split('/') |
|
597 |
- print "Usage: %s [-d] [-v] [-p seconds] [-u url]" % cmd_name[-1] |
|
598 |
- exit(-1) |
|
599 |
- index += 1 |
|
600 |
-##end def |
|
601 |
- |
|
602 |
-def main(): |
|
603 |
- """Handles timing of events and acts as executive routine managing |
|
604 |
- all other functions. |
|
605 |
- Parameters: none |
|
606 |
- Returns: nothing |
|
607 |
- """ |
|
608 |
- global dataRequestInterval |
|
609 |
- |
|
610 |
- signal.signal(signal.SIGTERM, terminateAgentProcess) |
|
611 |
- |
|
612 |
- print '%s starting up arednsig agent process' % \ |
|
613 |
- (getTimeStamp()) |
|
614 |
- |
|
615 |
- # last time output JSON file updated |
|
616 |
- lastDataRequestTime = -1 |
|
617 |
- # last time charts generated |
|
618 |
- lastChartUpdateTime = - 1 |
|
619 |
- # last time the rrdtool database updated |
|
620 |
- lastDatabaseUpdateTime = -1 |
|
621 |
- |
|
622 |
- ## Get command line arguments. |
|
623 |
- getCLarguments() |
|
624 |
- |
|
625 |
- requestIntervalSeconds = dataRequestInterval * 60 # convert to seconds |
|
626 |
- chartUpdateInterval = dataRequestInterval # get charts interval |
|
627 |
- |
|
628 |
- ## Exit with error if rrdtool database does not exist. |
|
629 |
- if not os.path.exists(_RRD_FILE): |
|
630 |
- print 'rrdtool database does not exist\n' \ |
|
631 |
- 'use createArednsigRrd script to ' \ |
|
632 |
- 'create rrdtool database\n' |
|
633 |
- exit(1) |
|
634 |
- |
|
635 |
- ## main loop |
|
636 |
- while True: |
|
637 |
- |
|
638 |
- currentTime = time.time() # get current time in seconds |
|
639 |
- |
|
640 |
- # Every web update interval request data from the aredn |
|
641 |
- # node and process the received data. |
|
642 |
- if currentTime - lastDataRequestTime > requestIntervalSeconds: |
|
643 |
- lastDataRequestTime = currentTime |
|
644 |
- ldData = [] |
|
645 |
- result = True |
|
646 |
- |
|
647 |
- # Get the data string from the device. |
|
648 |
- sData = getArednNodeData() |
|
649 |
- # If the first http request fails, try one more time. |
|
650 |
- if sData == None: |
|
651 |
- time.sleep(5) |
|
652 |
- sData = getArednNodeData() |
|
653 |
- if sData == None: |
|
654 |
- result = False |
|
655 |
- |
|
656 |
- # If successful parse the data. |
|
657 |
- if result: |
|
658 |
- result = parseNodeData(sData, ldData) |
|
659 |
- |
|
660 |
- # If parsing successful, convert the data. |
|
661 |
- if result: |
|
662 |
- result = convertData(ldData) |
|
663 |
- |
|
664 |
- # If conversion successful, write data to data files. |
|
665 |
- if result: |
|
666 |
- result = updateDatabase(ldData) |
|
667 |
- |
|
668 |
- if result: |
|
669 |
- writeOutputDataFile(sData, ldData) |
|
670 |
- |
|
671 |
- # Set the node status to online or offline depending on the |
|
672 |
- # success or failure of the above operations. |
|
673 |
- setNodeStatus(result) |
|
674 |
- |
|
675 |
- |
|
676 |
- # At the chart generation interval, generate charts. |
|
677 |
- if currentTime - lastChartUpdateTime > chartUpdateInterval: |
|
678 |
- lastChartUpdateTime = currentTime |
|
679 |
- p = multiprocessing.Process(target=generateGraphs, args=()) |
|
680 |
- p.start() |
|
681 |
- |
|
682 |
- # Relinquish processing back to the operating system until |
|
683 |
- # the next update interval. |
|
684 |
- |
|
685 |
- elapsedTime = time.time() - currentTime |
|
686 |
- if debugOption: |
|
687 |
- if result: |
|
688 |
- print "%s update successful:" % getTimeStamp(), |
|
689 |
- else: |
|
690 |
- print "%s update failed:" % getTimeStamp(), |
|
691 |
- print "%6f seconds processing time\n" % elapsedTime |
|
692 |
- remainingTime = requestIntervalSeconds - elapsedTime |
|
693 |
- if remainingTime > 0.0: |
|
694 |
- time.sleep(remainingTime) |
|
695 |
- ## end while |
|
696 |
- return |
|
697 |
-## end def |
|
698 |
- |
|
699 |
-if __name__ == '__main__': |
|
700 |
- try: |
|
701 |
- main() |
|
702 |
- except KeyboardInterrupt: |
|
703 |
- print '\n', |
|
704 |
- terminateAgentProcess('KeyboardInterrupt','Module') |
705 | 0 |
deleted file mode 100755 |
... | ... |
@@ -1,89 +0,0 @@ |
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: createArednsigRrd.py |
|
6 |
-# |
|
7 |
-# Description: Creates a rrdtool database for use by the weather agent to |
|
8 |
-# store the data from the weather station. The agent uses the data in the |
|
9 |
-# database to generate graphic charts for display in the weather station |
|
10 |
-# web page. |
|
11 |
-# |
|
12 |
-# Copyright 2020 Jeff Owrey |
|
13 |
-# This program is free software: you can redistribute it and/or modify |
|
14 |
-# it under the terms of the GNU General Public License as published by |
|
15 |
-# the Free Software Foundation, either version 3 of the License, or |
|
16 |
-# (at your option) any later version. |
|
17 |
-# |
|
18 |
-# This program is distributed in the hope that it will be useful, |
|
19 |
-# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
20 |
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
21 |
-# GNU General Public License for more details. |
|
22 |
-# |
|
23 |
-# You should have received a copy of the GNU General Public License |
|
24 |
-# along with this program. If not, see http://www.gnu.org/license. |
|
25 |
-# |
|
26 |
-# Revision History |
|
27 |
-# * v10 released 11 Jan 2020 by J L Owrey |
|
28 |
-# |
|
29 |
-import os |
|
30 |
-import time |
|
31 |
-import subprocess |
|
32 |
- |
|
33 |
- ### DEFINE FILE LOCATIONS ### |
|
34 |
- |
|
35 |
-_USER = os.environ['USER'] |
|
36 |
-# the file that stores the data |
|
37 |
-_RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER |
|
38 |
-_RRD_SIZE_IN_DAYS = 370 # days |
|
39 |
-_1YR_RRA_STEPS_PER_DAY = 96 |
|
40 |
-_DATABASE_UPDATE_INTERVAL = 60 |
|
41 |
- |
|
42 |
-def createRrdFile(): |
|
43 |
- """Create the rrd file if it does not exist. |
|
44 |
- Parameters: none |
|
45 |
- Returns: True, if successful |
|
46 |
- """ |
|
47 |
- |
|
48 |
- if os.path.exists(_RRD_FILE): |
|
49 |
- print "aredn node database already exists" |
|
50 |
- return True |
|
51 |
- |
|
52 |
- ## Calculate database size |
|
53 |
- |
|
54 |
- heartBeat = 2 * _DATABASE_UPDATE_INTERVAL |
|
55 |
- rra1yrNumPDP = int(round(86400 / (_1YR_RRA_STEPS_PER_DAY * \ |
|
56 |
- _DATABASE_UPDATE_INTERVAL))) |
|
57 |
- rrd24hrNumRows = int(round(86400 / _DATABASE_UPDATE_INTERVAL)) |
|
58 |
- rrd1yearNumRows = _1YR_RRA_STEPS_PER_DAY * _RRD_SIZE_IN_DAYS |
|
59 |
- |
|
60 |
- strFmt = ("rrdtool create %s --start now-1day --step %s " |
|
61 |
- "DS:S:GAUGE:%s:U:U DS:N:GAUGE:%s:U:U DS:SNR:GAUGE:%s:U:U " |
|
62 |
- "DS:RX_MCS:GAUGE:%s:U:U DS:TX_MCS:GAUGE:%s:U:U " |
|
63 |
- "DS:RX_RATE:GAUGE:%s:U:U DS:TX_RATE:GAUGE:%s:U:U " |
|
64 |
- "RRA:LAST:0.5:1:%s RRA:LAST:0.5:%s:%s") |
|
65 |
- |
|
66 |
- strCmd = strFmt % (_RRD_FILE, _DATABASE_UPDATE_INTERVAL, \ |
|
67 |
- heartBeat, heartBeat, heartBeat, heartBeat, \ |
|
68 |
- heartBeat, heartBeat, heartBeat, \ |
|
69 |
- rrd24hrNumRows, rra1yrNumPDP, rrd1yearNumRows) |
|
70 |
- |
|
71 |
- print "creating aredn node database...\n\n%s\n" % strCmd |
|
72 |
- |
|
73 |
- # Spawn a sub-shell and run the command |
|
74 |
- try: |
|
75 |
- subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \ |
|
76 |
- shell=True) |
|
77 |
- except subprocess.CalledProcessError, exError: |
|
78 |
- print "rrdtool create failed: %s" % (exError.output) |
|
79 |
- return False |
|
80 |
- return True |
|
81 |
-##end def |
|
82 |
- |
|
83 |
-def main(): |
|
84 |
- createRrdFile() |
|
85 |
-## end def |
|
86 |
- |
|
87 |
-if __name__ == '__main__': |
|
88 |
- main() |
|
89 |
- |
90 | 0 |
deleted file mode 100755 |
... | ... |
@@ -1,16 +0,0 @@ |
1 |
-#!/bin/sh |
|
2 |
-# |
|
3 |
-# Create a directory in the temporary file system for arednsig dynamic |
|
4 |
-# data. Set ownership and permissions to allow the Apache www-data user |
|
5 |
-# read and write access to this folder. |
|
6 |
-mkdir /tmp/arednsig |
|
7 |
-sudo chown :www-data /tmp/arednsig |
|
8 |
-chmod g+w /tmp/arednsig |
|
9 |
- |
|
10 |
-# Uncomment the following line if you choose to mount the dynamic |
|
11 |
-# folder to the folder created above. |
|
12 |
-#sudo mount --bind /tmp/arednsig /home/pi/public_html/arednsig/dynamic |
|
13 |
- |
|
14 |
-# Start arednsig agent |
|
15 |
-(sleep 5; /home/pi/bin/ardstart;) & |
|
16 |
- |
17 | 0 |
deleted file mode 100644 |
... | ... |
@@ -1,323 +0,0 @@ |
1 |
-<!DOCTYPE html> |
|
2 |
-<!-- Courtesy ruler for editing this file |
|
3 |
-12345678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
4 |
-<html> |
|
5 |
-<head> |
|
6 |
-<title>Node Signal</title> |
|
7 |
-<meta charset="UTF-8"> |
|
8 |
-<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
9 |
-<style> |
|
10 |
-body { |
|
11 |
- background-image: url("static/chalk.jpg"); |
|
12 |
-} |
|
13 |
-h2 { |
|
14 |
- font: bold 24px arial, sans-serif; |
|
15 |
-} |
|
16 |
-h3 { |
|
17 |
- font: bold 18px arial, sans-serif; |
|
18 |
-} |
|
19 |
-h4 { |
|
20 |
- font: bold 16px arial, sans-serif; |
|
21 |
-} |
|
22 |
-.mainContainer { |
|
23 |
- width: 750px; |
|
24 |
- text-align: center; |
|
25 |
- margin: auto; |
|
26 |
- /*border: 1px solid black;*/ |
|
27 |
-} |
|
28 |
-.datetime { |
|
29 |
- font: bold 22px arial, sans-serif; |
|
30 |
- padding: 0px; |
|
31 |
-} |
|
32 |
-.rowContainer { |
|
33 |
- display: table; |
|
34 |
- width: 100%; |
|
35 |
-} |
|
36 |
-.currentDataCell { |
|
37 |
- width: 50%; |
|
38 |
- padding: 10px; |
|
39 |
- font: bold 20px arial, sans-serif; |
|
40 |
- text-align: center; |
|
41 |
- display: table-cell; |
|
42 |
- vertical-align: middle; |
|
43 |
-} |
|
44 |
-.dataItems { |
|
45 |
- padding: 2px; |
|
46 |
- text-align: left; |
|
47 |
- line-height: 130%; |
|
48 |
- display: inline-block; |
|
49 |
- vertical-align: middle; |
|
50 |
-} |
|
51 |
-.chartContainer { |
|
52 |
- padding: 2px; |
|
53 |
-} |
|
54 |
-img.chart { |
|
55 |
- width:100%; |
|
56 |
-} |
|
57 |
-.notes { |
|
58 |
- font: 17px arial, sans-serif; |
|
59 |
- text-align: left; |
|
60 |
- padding: 10px; |
|
61 |
-} |
|
62 |
-span.chartSelector { |
|
63 |
- margin: auto; |
|
64 |
-} |
|
65 |
-ul.selectorElement { |
|
66 |
- list-style-type: none; |
|
67 |
- margin: 10px; |
|
68 |
- padding: 0; |
|
69 |
- overflow: hidden; |
|
70 |
- background-color: #bbb; |
|
71 |
- text-align: center; |
|
72 |
-} |
|
73 |
-li.selectorElement { |
|
74 |
- display: inline-block; |
|
75 |
- font: bold 18px arial, sans-serif; |
|
76 |
- color: black; |
|
77 |
-} |
|
78 |
-span.selectorElement:hover { |
|
79 |
- background-color: #333; |
|
80 |
- cursor: pointer; |
|
81 |
- color: white; |
|
82 |
-} |
|
83 |
-span.selectorElement { |
|
84 |
- display: inline-block; |
|
85 |
- padding: 8px 12px; |
|
86 |
-} |
|
87 |
-#iframe_a { |
|
88 |
- border:none; |
|
89 |
- width:100%; |
|
90 |
- height:450px; |
|
91 |
-} |
|
92 |
-</style> |
|
93 |
-</head> |
|
94 |
- |
|
95 |
-<body onload="main()"> |
|
96 |
- |
|
97 |
-<div class="mainContainer"> |
|
98 |
-<h2><a href="https://github.com/fractalxaos/ham/tree/master/arednsig" |
|
99 |
- style="text-decoration:none" target="_new"> |
|
100 |
-KA7JLO AREDN<sup>®</sup> Node Signal</a></h2> |
|
101 |
-<h3>Last Updated</h3> |
|
102 |
-<div class="datetime"> |
|
103 |
-<span id="date"></span> |
|
104 |
- |
|
105 |
-<span id="time"></span> |
|
106 |
-</div> |
|
107 |
- |
|
108 |
-<div class="rowContainer"> |
|
109 |
-<div class="currentDataCell"> |
|
110 |
-<div class="dataItems" style="text-align: center;"> |
|
111 |
-Status: <span id="status"></span><br> |
|
112 |
-Data updates every: <span id="period"></span> minutes |
|
113 |
-</div> |
|
114 |
- |
|
115 |
-</div> |
|
116 |
-</div> |
|
117 |
- |
|
118 |
-<span class="chartSelectors"> |
|
119 |
-<ul class="selectorElement"> |
|
120 |
-<li class="selectorElement">Select charts:</li> |
|
121 |
-<li class="selectorElement"><span class="selectorElement" |
|
122 |
- onclick="setChartPeriod(1)">24 hours</span></li> |
|
123 |
-<li class="selectorElement"><span class="selectorElement" |
|
124 |
- onclick="setChartPeriod(2)">4 weeks</span></li> |
|
125 |
-<li class="selectorElement"><span class="selectorElement" |
|
126 |
- onclick="setChartPeriod(3)">12 months</span></li> |
|
127 |
-<li class="selectorElement"><span class="selectorElement" |
|
128 |
- onclick="setChartPeriod(0)">Custom…</span></li> |
|
129 |
-</ul> |
|
130 |
-</span> |
|
131 |
- |
|
132 |
-<div class="rowContainer" id="customChartsContainer" style="display:none;"> |
|
133 |
-<div class="currentDataCell"> |
|
134 |
-<form id="fmDateSelector" action="arednsig.php" method="post" |
|
135 |
- target="iframe_a"> |
|
136 |
-<label for="beginDate">Begin Date: </label> |
|
137 |
-<input id="beginDate" name="beginDate" type="date" value="mm/dd/yyyy" /> |
|
138 |
-<label for="endDate">End Date: </label> |
|
139 |
-<input id="endDate" name="endDate" type="date" value="mm/dd/yyyy" /> |
|
140 |
-<br><br> |
|
141 |
-<input type="button" onclick="getCustomCharts()" value="Get Charts"> |
|
142 |
-</form> |
|
143 |
-<span id="errorMsg"></span><br> |
|
144 |
-<iframe id="iframe_a" name="iframe_a"></iframe> |
|
145 |
-</div> |
|
146 |
-</div> |
|
147 |
- |
|
148 |
-<br> |
|
149 |
- |
|
150 |
-<div class="rowContainer" id="stockChartsContainer"> |
|
151 |
-<div class="chartContainer"> |
|
152 |
-<img class="chart" id="signalChart"> |
|
153 |
-</div> |
|
154 |
-<div class="chartContainer"> |
|
155 |
-<img class="chart" id="snrChart"> |
|
156 |
-</div> |
|
157 |
-</div> |
|
158 |
- |
|
159 |
-<div class="notes"> |
|
160 |
-<b>NOTES:</b> |
|
161 |
-<ul> |
|
162 |
-<li>Aredn Node Signal software available at |
|
163 |
-<a href="https://github.com/fractalxaos/ham/tree/master/arednsig" |
|
164 |
- target="_new"> |
|
165 |
-<i>Github.com</i></a>.</li> |
|
166 |
-<li>Project sponsored by |
|
167 |
-<a href="https://willamettevalleymesh.net" TARGET="_NEW"> |
|
168 |
-<i>Willamette Valley Mesh Network</i></a>, Salem, Oregon.</li> |
|
169 |
-<li>For more information about the amateur radio emergency |
|
170 |
- data network (AREDN) see official web site at |
|
171 |
- <a href="http://www.arednmesh.org" target="_blank"> |
|
172 |
-www.arednmesh.org</a>.</li> |
|
173 |
-</ul> |
|
174 |
-</div> |
|
175 |
-</div> |
|
176 |
-<br> |
|
177 |
- |
|
178 |
-<script> |
|
179 |
- |
|
180 |
-/* Global constants */ |
|
181 |
- |
|
182 |
-var nodeDataUrl = "dynamic/nodeOnline.js"; |
|
183 |
- |
|
184 |
-/* Global DOM objects */ |
|
185 |
- |
|
186 |
-// Chart elements |
|
187 |
-var signalChart = document.getElementById("signalChart"); |
|
188 |
-var snrChart = document.getElementById("snrChart"); |
|
189 |
- |
|
190 |
-// Text elements |
|
191 |
-var dateElmt = document.getElementById("date"); |
|
192 |
-var timeElmt = document.getElementById("time"); |
|
193 |
-var statusElmt = document.getElementById("status"); |
|
194 |
-var periodElmt = document.getElementById("period"); |
|
195 |
- |
|
196 |
-// Document elements |
|
197 |
-var customChartsContainer = document.getElementById("customChartsContainer"); |
|
198 |
-var stockChartsContainer = document.getElementById("stockChartsContainer"); |
|
199 |
-var fmDateSelector = document.getElementById("fmDateSelector"); |
|
200 |
-var errorMsg = document.getElementById("errorMsg"); |
|
201 |
- |
|
202 |
-/* Global objects */ |
|
203 |
- |
|
204 |
-var httpRequest = new XMLHttpRequest(); |
|
205 |
- |
|
206 |
-/* Global variables */ |
|
207 |
- |
|
208 |
-var chartPeriod = 1; |
|
209 |
-var chartRefreshRate = 0; // chart refresh rate in minutes |
|
210 |
- |
|
211 |
-function main() { |
|
212 |
- /* Register call back function to process http requests */ |
|
213 |
- httpRequest.onreadystatechange = function() { |
|
214 |
- if (httpRequest.readyState == 4 && httpRequest.status == 200) { |
|
215 |
- var dataArray = JSON.parse(httpRequest.responseText); |
|
216 |
- displayData(dataArray[0]); |
|
217 |
- } else if (httpRequest.readyState == 4 && httpRequest.status == 404) { |
|
218 |
- displayOfflineStatus(); |
|
219 |
- } |
|
220 |
- }; |
|
221 |
- httpRequest.ontimeout = function(e) { |
|
222 |
- displayOfflineStatus(); |
|
223 |
- }; |
|
224 |
- |
|
225 |
- initializeDateSelector(); |
|
226 |
- getNodeData(); |
|
227 |
- getNodeCharts(); |
|
228 |
-} |
|
229 |
- |
|
230 |
-function getNodeData() { |
|
231 |
- httpRequest.open("GET", nodeDataUrl, true); |
|
232 |
- httpRequest.timeout = 3000; |
|
233 |
- httpRequest.send(); |
|
234 |
-} |
|
235 |
- |
|
236 |
-function setChartPeriod(n) { |
|
237 |
- chartPeriod = n; |
|
238 |
- if (n == 0) { |
|
239 |
- customChartsContainer.style.display = "block"; |
|
240 |
- stockChartsContainer.style.display = "none"; |
|
241 |
- } else { |
|
242 |
- customChartsContainer.style.display = "none"; |
|
243 |
- stockChartsContainer.style.display = "block"; |
|
244 |
- getNodeCharts(); |
|
245 |
- } |
|
246 |
-} |
|
247 |
- |
|
248 |
-function getNodeCharts() { |
|
249 |
- var d = new Date; |
|
250 |
- var pfx; |
|
251 |
- |
|
252 |
- switch(chartPeriod) { |
|
253 |
- case 1: |
|
254 |
- pfx = "24hr_"; |
|
255 |
- break; |
|
256 |
- case 2: |
|
257 |
- pfx = "4wk_"; |
|
258 |
- break; |
|
259 |
- case 3: |
|
260 |
- pfx = "12m_"; |
|
261 |
- break; |
|
262 |
- } |
|
263 |
- signalChart.src = "dynamic/" + pfx + "signal.png?ver=" + d.getTime(); |
|
264 |
- snrChart.src = "dynamic/" + pfx + "snr.png?ver=" + d.getTime(); |
|
265 |
-} |
|
266 |
- |
|
267 |
-function displayData(dataItem) { |
|
268 |
- var timeStamp, date, time, hourminute; |
|
269 |
- var localDateObj,localTimeZone; |
|
270 |
- |
|
271 |
- timeStamp = dataItem.date; |
|
272 |
- date = timeStamp.split(" ")[0]; |
|
273 |
- time = timeStamp.split(" ")[1]; |
|
274 |
- hourminute = time.split(":")[0] + ":" + time.split(":")[1]; |
|
275 |
- localDate = new Date(); |
|
276 |
- localTimeZone = localDate.getTimezoneOffset() / 60; |
|
277 |
- dateElmt.innerHTML = date; |
|
278 |
- timeElmt.innerHTML = hourminute + |
|
279 |
- " <small>(GMT+" + localTimeZone + ")</small>"; |
|
280 |
- |
|
281 |
- statusElmt.innerHTML = "Online"; |
|
282 |
- statusElmt.style.color = "green"; |
|
283 |
- |
|
284 |
- chartRefreshRate = dataItem.period; |
|
285 |
- periodElmt.innerHTML = chartRefreshRate; |
|
286 |
- setInterval(getNodeData, 60000 * chartRefreshRate); |
|
287 |
- setInterval(getNodeCharts, 60000 * chartRefreshRate); |
|
288 |
-} |
|
289 |
- |
|
290 |
-function displayOfflineStatus() { |
|
291 |
- var d = new Date(); |
|
292 |
- localTimeZone = d.getTimezoneOffset() / 60; |
|
293 |
- dateElmt.innerHTML = (d.getMonth() + 1) + "/" + d.getDate() + "/" + |
|
294 |
- d.getFullYear(); |
|
295 |
- timeElmt.innerHTML = d.getHours() + ":" + d.getMinutes() + |
|
296 |
- " <small>(GMT+" + localTimeZone + ")</small>"; |
|
297 |
- periodElmt.innerHTML = "?"; |
|
298 |
- statusElmt.innerHTML = "offline"; |
|
299 |
- statusElmt.style.color = "red"; |
|
300 |
-} |
|
301 |
- |
|
302 |
-function initializeDateSelector() { |
|
303 |
- var d = new Date(); |
|
304 |
- |
|
305 |
- var dEnd = new Date(d.getFullYear(), |
|
306 |
- d.getMonth(), d.getDate() - 0); |
|
307 |
- |
|
308 |
- var dBegin = new Date(d.getFullYear(), |
|
309 |
- d.getMonth(), d.getDate() - 1); |
|
310 |
- |
|
311 |
- document.getElementById("beginDate").valueAsDate = dBegin; |
|
312 |
- document.getElementById("endDate").valueAsDate = dEnd; |
|
313 |
-} |
|
314 |
- |
|
315 |
-function getCustomCharts() { |
|
316 |
- fmDateSelector.submit(); |
|
317 |
-} |
|
318 |
-</script> |
|
319 |
- |
|
320 |
-</body> |
|
321 |
-</html> |
|
322 |
- |
323 | 0 |
deleted file mode 100644 |
... | ... |
@@ -1,205 +0,0 @@ |
1 |
-<html> |
|
2 |
-<!-- Courtsey ruler |
|
3 |
-12345678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
4 |
-<head> |
|
5 |
-<style> |
|
6 |
-p { |
|
7 |
- font: 14px ariel, sans serif; |
|
8 |
-} |
|
9 |
-#errorMsg { |
|
10 |
- font:bold 18px arial,sans-serif; |
|
11 |
- color:red; |
|
12 |
- text-align:center; |
|
13 |
-} |
|
14 |
-.chartContainer { |
|
15 |
- padding: 2px; |
|
16 |
-} |
|
17 |
-img.chart { |
|
18 |
- width:100%; |
|
19 |
-} |
|
20 |
-</style> |
|
21 |
-</head> |
|
22 |
-<body> |
|
23 |
- |
|
24 |
-<?php |
|
25 |
-/* |
|
26 |
- Script: arednsig.php |
|
27 |
- |
|
28 |
- Description: This scripts generates on the server charts showing |
|
29 |
- signal data spanning the period supplied by the user. The script |
|
30 |
- does the following: |
|
31 |
- - converts user supplied dates to epoch time |
|
32 |
- - gets the times of the first and last data point in the round |
|
33 |
- robin database (RRD) |
|
34 |
- - from above validates user supplied begin and end dates |
|
35 |
- - creates charts of the specified period |
|
36 |
- |
|
37 |
- Copyright 2020 Jeff Owrey |
|
38 |
- This program is free software: you can redistribute it and/or modify |
|
39 |
- it under the terms of the GNU General Public License as published by |
|
40 |
- the Free Software Foundation, either version 3 of the License, or |
|
41 |
- (at your option) any later version. |
|
42 |
- |
|
43 |
- This program is distributed in the hope that it will be useful, |
|
44 |
- but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
45 |
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
46 |
- GNU General Public License for more details. |
|
47 |
- |
|
48 |
- You should have received a copy of the GNU General Public License |
|
49 |
- along with this program. If not, see http://www.gnu.org/license. |
|
50 |
- |
|
51 |
- Revision History |
|
52 |
- * v20 released 18 Jan 2020 by J L Owrey; first release |
|
53 |
-*/ |
|
54 |
- |
|
55 |
-# Define global constants |
|
56 |
- |
|
57 |
-# round robin database file |
|
58 |
-define("_RRD_FILE", str_replace("public_html/arednsig/arednsig.php", |
|
59 |
- "database/arednsigData.rrd", |
|
60 |
- $_SERVER["SCRIPT_FILENAME"])); |
|
61 |
-# charts html directory |
|
62 |
-define("_CHART_DIRECTORY", str_replace("arednsig.php", |
|
63 |
- "dynamic/", |
|
64 |
- $_SERVER["SCRIPT_FILENAME"])); |
|
65 |
-# standard chart width in pixels |
|
66 |
-define("_CHART_WIDTH", 600); |
|
67 |
-# standard chart height in pixels |
|
68 |
-define("_CHART_HEIGHT", 150); |
|
69 |
-# debug mode |
|
70 |
-define("_DEBUG", false); |
|
71 |
- |
|
72 |
-# Set error handling modes. |
|
73 |
-error_reporting(E_ALL); |
|
74 |
- |
|
75 |
-# Get user supplied chart begin and end dates. |
|
76 |
-$beginDate = $_POST["beginDate"]; |
|
77 |
-$endDate = $_POST["endDate"]; |
|
78 |
- |
|
79 |
-# Convert the user supplied dates to epoch time stamps. |
|
80 |
-$beginDateEp = strtotime($beginDate); |
|
81 |
-$endDateEp = strtotime($endDate); |
|
82 |
- |
|
83 |
-# Get the time stamp of the earliest data point in the RRD file. |
|
84 |
-$cmd = sprintf("rrdtool first %s --rraindex 1", _RRD_FILE); |
|
85 |
-$firstDP = shell_exec($cmd); |
|
86 |
- |
|
87 |
-# Get the time stamp of the latest data point in the RRD file. |
|
88 |
-$cmd = sprintf("rrdtool last %s", _RRD_FILE); |
|
89 |
-$lastDP = shell_exec($cmd); |
|
90 |
- |
|
91 |
-# Determine validity of user supplied dates. User supplied begin |
|
92 |
-# date must be less than user supplied end date. Furthermore both |
|
93 |
-# dates must be within the range of dates stored in the RRD. |
|
94 |
-if ($beginDateEp > $endDateEp) { |
|
95 |
- echo "<p id=\"errorMsg\">" . |
|
96 |
- "End date must be after begin date.</p>"; |
|
97 |
-} elseif ($beginDateEp < $firstDP || $endDateEp > $lastDP) { |
|
98 |
- echo "<p id=\"errorMsg\">" . |
|
99 |
- "Date range must be between " . |
|
100 |
- date('m / d / Y', $firstDP) . " and " . |
|
101 |
- date('m / d / Y', $lastDP) . ".</p>"; |
|
102 |
-} else { |
|
103 |
- # Generate charts from validated user supplied dates. |
|
104 |
- if (_DEBUG) { |
|
105 |
- echo "<p>Date range: " . $beginDateEp . " thru " . |
|
106 |
- $endDateEp . "</p>"; |
|
107 |
- } |
|
108 |
- createChart('custom_signal', 'S', 'dBm', |
|
109 |
- 'RSSI', $beginDateEp, $endDateEp, |
|
110 |
- 0, 0, 2, false); |
|
111 |
- createChart('custom_snr', 'SNR', 'dBm', |
|
112 |
- 'S/N', $beginDateEp, $endDateEp, |
|
113 |
- 0, 0, 2, false); |
|
114 |
- # Send html commands to client browser. |
|
115 |
- echo "<div class=\"chartContainer\">" . |
|
116 |
- "<img class=\"chart\" src=\"dynamic/custom_signal.png\">" . |
|
117 |
- "</div>"; |
|
118 |
- echo "<div class=\"chartContainer\">" . |
|
119 |
- "<img class=\"chart\" src=\"dynamic/custom_snr.png\">" . |
|
120 |
- "</div>"; |
|
121 |
-} |
|
122 |
- |
|
123 |
-function createChart($chartFile, $dataItem, $label, $title, $begin, |
|
124 |
- $end, $lower, $upper, $addTrend, $autoScale) { |
|
125 |
- /* |
|
126 |
- Uses rrdtool to create a chart of specified aredn node data item. |
|
127 |
- Parameters: |
|
128 |
- fileName - name of the created chart file |
|
129 |
- dataItem - data item to be charted |
|
130 |
- label - string containing a label for the item to be charted |
|
131 |
- title - string containing a title for the chart |
|
132 |
- begin - beginning time of the chart data |
|
133 |
- end - ending time of the data to be charted |
|
134 |
- lower - lower bound for chart ordinate #NOT USED |
|
135 |
- upper - upper bound for chart ordinate #NOT USED |
|
136 |
- addTrend - 0, show only chart data |
|
137 |
- 1, show only a trend line |
|
138 |
- 2, show a trend line and the chart data |
|
139 |
- autoScale - if True, then use vertical axis auto scaling |
|
140 |
- (lower and upper parameters are ignored), otherwise use |
|
141 |
- lower and upper parameters to set vertical axis scale |
|
142 |
- Returns: True if successful, False otherwise |
|
143 |
- */ |
|
144 |
- |
|
145 |
- # Define path on server to chart files. |
|
146 |
- $chartPath = _CHART_DIRECTORY . $chartFile . ".png"; |
|
147 |
- |
|
148 |
- # Format the rrdtool chart command. |
|
149 |
- |
|
150 |
- # Set chart file name, start time, end time, height, and width. |
|
151 |
- $cmdfmt = "rrdtool graph %s -a PNG -s %s -e %s -w %s -h %s "; |
|
152 |
- $cmd = sprintf($cmdfmt, $chartPath, $begin, $end, _CHART_WIDTH, |
|
153 |
- _CHART_HEIGHT); |
|
154 |
- $cmdfmt = "-l %s -u %s -r "; |
|
155 |
- |
|
156 |
- # Set upper and lower ordinate bounds. |
|
157 |
- if ($lower < $upper) { |
|
158 |
- $cmd .= sprintf($cmdfmt, $lower, $upper); |
|
159 |
- } elseif ($autoScale) { |
|
160 |
- $cmd .= "-A "; |
|
161 |
- } |
|
162 |
- $cmd .= "-Y "; |
|
163 |
- |
|
164 |
- # Set the chart ordinate label and chart title. |
|
165 |
- $cmdfmt = "-v %s -t %s "; |
|
166 |
- $cmd .= sprintf($cmdfmt, $label, $title); |
|
167 |
- |
|
168 |
- # Define moving average window width. |
|
169 |
- $trendWindow = floor(($end - $begin) / 12); |
|
170 |
- |
|
171 |
- # Show the data, or a moving average trend line over |
|
172 |
- # the data, or both. |
|
173 |
- $cmdfmt = "DEF:dSeries=%s:%s:LAST "; |
|
174 |
- $cmd .= sprintf($cmdfmt, _RRD_FILE, $dataItem); |
|
175 |
- if ($addTrend == 0) { |
|
176 |
- $cmd .= "LINE1:dSeries#0400ff "; |
|
177 |
- } elseif ($addTrend == 1) { |
|
178 |
- $cmdfmt = "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 "; |
|
179 |
- $cmd .= sprintf($cmdfmt, $trendWindow); |
|
180 |
- } elseif ($addTrend == 2) { |
|
181 |
- $cmd .= "LINE1:dSeries#0400ff "; |
|
182 |
- $cmdfmt = "CDEF:smoothed=dSeries,%s,TREND LINE3:smoothed#ff0000 "; |
|
183 |
- #$cmdfmt = "CDEF:smoothed=dSeries,%s,XYZZY LINE3:smoothed#ff0000 "; |
|
184 |
- $cmd .= sprintf($cmdfmt, $trendWindow); |
|
185 |
- } |
|
186 |
- |
|
187 |
- # Execute the formatted rrdtool command in the shell. The rrdtool |
|
188 |
- # command will complete execution before the html image tags get |
|
189 |
- # sent to the browser. This assures that the charts are available |
|
190 |
- # when the client browser executes the html code that loads the |
|
191 |
- # charts into the document displayed by the client browser. |
|
192 |
- if (_DEBUG) { |
|
193 |
- echo "<p>chart command:<br>" . $cmd . "</p>"; |
|
194 |
- } |
|
195 |
- $result = shell_exec($cmd . " 2>&1"); |
|
196 |
- if (_DEBUG) { |
|
197 |
- echo "<p>result:<br>" . $result . "</p>"; |
|
198 |
- } |
|
199 |
-} |
|
200 |
- |
|
201 |
-?> |
|
202 |
- |
|
203 |
-</body> |
|
204 |
-</html> |