Browse code

custom charts added

gandolf authored on 01/17/2020 00:20:38
Showing 3 changed files
... ...
@@ -5,7 +5,7 @@
5 5
 # Module: arednsigAgent.py
6 6
 #
7 7
 # Description: This module acts as an agent between the aredn node
8
-# and aredn mesh web services.  The agent periodically sends an http
8
+# and aredn mest services.  The agent periodically sends an http
9 9
 # request to the aredn node, processes the response from
10 10
 # the node, and performs a number of operations:
11 11
 #     - conversion of data items
... ...
@@ -57,8 +57,8 @@ _DOCROOT_PATH = "/home/%s/public_html/arednsig/" % _USER
57 57
 _CHARTS_DIRECTORY = _DOCROOT_PATH + "dynamic/"
58 58
 # location of data output file
59 59
 _OUTPUT_DATA_FILE = _DOCROOT_PATH + "dynamic/arednsigOutputData.js"
60
-# small size output data file for heartbeat signal to html docs
61
-_SMALL_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
60
+# dummy output data file
61
+_DUMMY_OUTPUT_FILE = _DOCROOT_PATH + "dynamic/nodeOnline.js"
62 62
 # database that stores radmon data
63 63
 _RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
64 64
 
... ...
@@ -74,6 +74,8 @@ _HTTP_REQUEST_TIMEOUT = 10
74 74
 _CHART_WIDTH = 600
75 75
 # standard chart height in pixels
76 76
 _CHART_HEIGHT = 150
77
+# source of time stamp attached to output data file
78
+_USE_NODE_TIMESTAMP = True
77 79
 
78 80
    ### GLOBAL VARIABLES ###
79 81
 
... ...
@@ -89,7 +91,9 @@ failedUpdateCount = 0
89 91
 # detected status of aredn node device
90 92
 nodeOnline = True
91 93
 
92
-# network address of aredn node
94
+# status of reset command to aredn node
95
+remoteDeviceReset = False
96
+# ip address of aredn node
93 97
 arednNodeUrl = _DEFAULT_AREDN_NODE_URL
94 98
 # frequency of data requests to aredn node
95 99
 dataRequestInterval = _DEFAULT_DATA_REQUEST_INTERVAL
... ...
@@ -142,8 +146,8 @@ def setStatusToOffline():
142 146
     # Inform downstream clients by removing output data file.
143 147
     if os.path.exists(_OUTPUT_DATA_FILE):
144 148
        os.remove(_OUTPUT_DATA_FILE)
145
-    if os.path.exists(_SMALL_OUTPUT_FILE):
146
-       os.remove(_SMALL_OUTPUT_FILE)
149
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
150
+       os.remove(_DUMMY_OUTPUT_FILE)
147 151
     # If the aredn node was previously online, then send
148 152
     # a message that we are now offline.
149 153
     if nodeOnline:
... ...
@@ -162,8 +166,8 @@ def terminateAgentProcess(signal, frame):
162 166
     # Inform downstream clients by removing output data file.
163 167
     if os.path.exists(_OUTPUT_DATA_FILE):
164 168
        os.remove(_OUTPUT_DATA_FILE)
165
-    if os.path.exists(_SMALL_OUTPUT_FILE):
166
-       os.remove(_SMALL_OUTPUT_FILE)
169
+    if os.path.exists(_DUMMY_OUTPUT_FILE):
170
+       os.remove(_DUMMY_OUTPUT_FILE)
167 171
     print '%s terminating arednsig agent process' % \
168 172
               (getTimeStamp())
169 173
     sys.exit(0)
... ...
@@ -188,8 +192,8 @@ def getArednNodeData():
188 192
         del conn
189 193
 
190 194
     except Exception, exError:
191
-        # If no response is received from the node, then assume that
192
-        # the node is down or unavailable over the network.  In
195
+        # If no response is received from the device, then assume that
196
+        # the device is down or unavailable over the network.  In
193 197
         # that case return None to the calling function.
194 198
         print "%s http error: %s" % (getTimeStamp(), exError)
195 199
         return None
... ...
@@ -205,23 +209,11 @@ def parseNodeData(sData, ldData):
205 209
        into its component parts.  
206 210
        Parameters:
207 211
            sData - the string containing the data to be parsed
208
-           ldData - a list object to contain the parsed data items
212
+           dData - a dictionary object to contain the parsed data items
209 213
        Returns: True if successful, False otherwise
210 214
     """
211
-    # Only interested in datapoints that have arrived during the
212
-    # current update cycle defined by the data request interval.
213
-    # The default is sixty minutes, and the node records a data point
214
-    # once a minute.  Therefore, in the default case, extract the last
215
-    # sixty data points from the node's response.  Otherwise, extract
216
-    # a number of datapoints equal the the data request interval
217
-    # (in minutes). 
218 215
     iTrail = int(dataRequestInterval)
219 216
 
220
-    # The node response with json object containing 24 hours worth
221
-    # of datapoints.  Each data point is, itself, a json object.
222
-    # The following code converts the json object to a python list
223
-    # object containing dictionary objects.  Each dictionary object
224
-    # stores one data point.
225 217
     try:
226 218
         ldTmp = json.loads(sData[1:-1])
227 219
         ldTmp = ldTmp[-iTrail:]
... ...
@@ -230,9 +222,7 @@ def parseNodeData(sData, ldData):
230 222
     except Exception, exError:
231 223
         print "%s parse failed: %s" % (getTimeStamp(), exError)
232 224
         return False
233
-       
234
-    # Store the dictionary objects in the list object passed by
235
-    # reference to this function.
225
+    
236 226
     del ldData[:]
237 227
     for item in ldTmp:
238 228
         ldData.append(item)
... ...
@@ -245,7 +235,7 @@ def parseNodeData(sData, ldData):
245 235
 def convertData(ldData):
246 236
     """Convert individual node signal data items as necessary.
247 237
        Parameters:
248
-           ldData - a list object containing the node signal data
238
+           dData - a dictionary object containing the node signal data
249 239
        Returns: True if successful, False otherwise
250 240
     """
251 241
     # parse example string
... ...
@@ -255,9 +245,6 @@ def convertData(ldData):
255 245
     #
256 246
     for item in ldData:
257 247
         try:
258
-            # Convert data items to the required data types
259
-            # (all integer in this case).  Change the dictionary
260
-            # object keys to more friendly names.
261 248
             item['time'] = int(item.pop('x')) / 1000
262 249
             item['signal'] = int(item['y'][0])
263 250
             item['noise'] = int(item['y'][1])
... ...
@@ -282,8 +269,8 @@ def updateDatabase(ldData):
282 269
     Update the rrdtool database by executing an rrdtool system command.
283 270
     Format the command using the data extracted from the aredn node
284 271
     response.   
285
-    Parameters: ldData - a list object containing data items to be
286
-                        written to the RRD file
272
+    Parameters: dData - dictionary object containing data items to be
273
+                        written to the rr database file
287 274
     Returns: True if successful, False otherwise
288 275
     """
289 276
     updateCount = 0
... ...
@@ -293,9 +280,7 @@ def updateDatabase(ldData):
293 280
          print "updating database..."
294 281
 
295 282
     for item in ldData:
296
-        # Throw out data points that have a time stamp earlier
297
-        # than the last entry in the round-robin database (RRD).
298
-        # rrdtool will throw an error in this case.
283
+
299 284
         if item['time'] <= lastDataPointTime:
300 285
             if verboseDebug:
301 286
                 print "%s invalid timestamp: discarding data" % \
... ...
@@ -349,7 +334,7 @@ def writeOutputDataFile(sData, ldData):
349 334
     sDate = "[{\"date\":\"%s\",\"period\":\"%s\"}]" % \
350 335
            (lastUpdate, dataRequestInterval)
351 336
     try:
352
-        fc = open(_SMALL_OUTPUT_FILE, "w")
337
+        fc = open(_DUMMY_OUTPUT_FILE, "w")
353 338
         fc.write(sDate)
354 339
         fc.close()
355 340
     except Exception, exError:
... ...
@@ -1,7 +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
4
-# log file.
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.
5 4
 #
6 5
 # Module: createArednsigRrd.py
7 6
 #
... ...
@@ -27,7 +26,6 @@
27 26
 # Revision History
28 27
 #   * v10 released 11 Jan 2020 by J L Owrey
29 28
 #
30
-#2345678901234567890123456789012345678901234567890123456789012345678901234567890
31 29
 import os
32 30
 import time
33 31
 import subprocess
... ...
@@ -35,11 +33,8 @@ import subprocess
35 33
     ### DEFINE FILE LOCATIONS ###
36 34
 
37 35
 _USER = os.environ['USER']
38
-# the RRD file that stores the data
36
+# the file that stores the data
39 37
 _RRD_FILE = "/home/%s/database/arednsigData.rrd" % _USER
40
-
41
-    ### DEFINE DATABASE GRANULARITY AND TIME SPAN ###
42
-
43 38
 _RRD_SIZE_IN_DAYS = 370 # days
44 39
 _1YR_RRA_STEPS_PER_DAY = 96
45 40
 _DATABASE_UPDATE_INTERVAL = 60
... ...
@@ -49,6 +44,7 @@ def createRrdFile():
49 44
        Parameters: none
50 45
        Returns: True, if successful
51 46
     """
47
+
52 48
     if os.path.exists(_RRD_FILE):
53 49
         print "aredn node database already exists"
54 50
         return True
... ...
@@ -61,8 +57,6 @@ def createRrdFile():
61 57
     rrd24hrNumRows = int(round(86400 / _DATABASE_UPDATE_INTERVAL))
62 58
     rrd1yearNumRows = _1YR_RRA_STEPS_PER_DAY * _RRD_SIZE_IN_DAYS
63 59
        
64
-    ## Format rrdtool command to create RRD database
65
-     
66 60
     strFmt = ("rrdtool create %s --start now-1day --step %s "
67 61
               "DS:S:GAUGE:%s:U:U DS:N:GAUGE:%s:U:U DS:SNR:GAUGE:%s:U:U "
68 62
               "DS:RX_MCS:GAUGE:%s:U:U DS:TX_MCS:GAUGE:%s:U:U "
... ...
@@ -74,10 +68,9 @@ def createRrdFile():
74 68
                 heartBeat,  heartBeat, heartBeat,            \
75 69
                 rrd24hrNumRows, rra1yrNumPDP, rrd1yearNumRows)
76 70
 
77
-    ## Run the command as a shell subprocess
78
-
79 71
     print "creating aredn node database...\n\n%s\n" % strCmd
80 72
 
73
+    # Spawn a sub-shell and run the command
81 74
     try:
82 75
         subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \
83 76
                                 shell=True)
... ...
@@ -24,6 +24,7 @@ h4 {
24 24
     width: 750px;
25 25
     text-align: center;
26 26
     margin: auto;
27
+    /*border: 1px solid black;*/
27 28
 }
28 29
 .datetime {
29 30
     font: bold 22px arial, sans-serif;
... ...
@@ -40,7 +41,6 @@ h4 {
40 41
     text-align: center;
41 42
     display: table-cell;
42 43
     vertical-align: middle;
43
-    /*border: 1px solid black;*/
44 44
 }
45 45
 .dataItems {
46 46
     padding: 2px;
... ...
@@ -60,10 +60,10 @@ img.chart {
60 60
     text-align: left;
61 61
     padding: 10px;
62 62
 }
63
-span.chartNav {
63
+span.navContainer {
64 64
     margin: auto;
65 65
 }
66
-ul.chartNav {
66
+ul.navElement {
67 67
     list-style-type: none;
68 68
     margin: 10px;
69 69
     padding: 0;
... ...
@@ -71,20 +71,25 @@ ul.chartNav {
71 71
     background-color: #bbb;
72 72
     text-align: center;
73 73
 }
74
-li.chartNav {
74
+li.navElement {
75 75
     display: inline-block;
76 76
     font: bold 18px arial, sans-serif;
77 77
     color: black;
78 78
 }
79
-text.chartNav:hover {
79
+span.navElement:hover {
80 80
     background-color: #333;
81 81
     cursor: pointer;
82 82
     color: white;
83 83
 }
84
-text.chartNav {
84
+span.navElement {
85 85
     display: inline-block;
86 86
     padding: 8px 12px;
87 87
 }
88
+#iframe_a {
89
+    border:none;
90
+    width:100%;
91
+    height:450px;
92
+}
88 93
 </style>
89 94
 </head>
90 95
 
... ...
@@ -97,9 +102,9 @@ style="text-decoration:none" target="_new">
97 102
 KA7JLO Aredn Node Signal</a></h2>
98 103
 <h3>Last Updated</h3>
99 104
 <div class="datetime">
100
-<text id="date"></text>
105
+<span id="date"></span>
101 106
 &nbsp;&nbsp;
102
-<text id="time"></text>
107
+<span id="time"></span>
103 108
 </div>
104 109
 
105 110
 <div class="rowContainer">
... ...
@@ -109,43 +114,51 @@ Status:<br>
109 114
 Page updates every:
110 115
 </div>
111 116
 <div class="dataItems">
112
-<text id="status"></text><br>
113
-<text id="period"></text> minutes
117
+<span id="status"></span><br>
118
+<span id="period"></span> minutes
114 119
 </div>
115 120
 </div>
116 121
 </div>
117 122
 
118
-<span class="chartNav">
119
-<ul class="chartNav">
120
-<li class="chartNav">Select charts:</li>
121
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(1)">
122
-24 hours</text></li>
123
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(2)">
124
-4 weeks</text></li>
125
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(3)">
126
-12 months</text></li>
123
+<span class="navContainer">
124
+<ul class="navElement">
125
+<li class="navElement">Select charts:</li>
126
+<li class="navElement"><span class="navElement" onclick="setChartPeriod(1)">
127
+24 hours</span></li>
128
+<li class="navElement"><span class="navElement" onclick="setChartPeriod(2)">
129
+4 weeks</span></li>
130
+<li class="navElement"><span class="navElement" onclick="setChartPeriod(3)">
131
+12 months</span></li>
132
+<li class="navElement"><span class="navElement" onclick="setChartPeriod(0)">
133
+Custom...</span></li>
127 134
 </ul>
128 135
 </span>
129
-<br>
130 136
 
131
-<div class="chartContainer">
132
-<img class="chart" id="snrChart">
137
+<div class="rowContainer" id="customChartsContainer" style="display:none;">
138
+<div class="currentDataCell">
139
+<form id="fmDateSelector" action="arednsig.php" method="post"
140
+ target="iframe_a">
141
+<label for="beginDate">Begin Date: </label>
142
+<input id="beginDate" name="beginDate" type="date" value="mm/dd/yyyy" />
143
+<label for="endDate">End Date: </label>
144
+<input id="endDate" name="endDate" type="date" value="mm/dd/yyyy" />
145
+<br><br>
146
+<input type="button" onclick="getCustomCharts()" value="Get Charts">
147
+</form>
148
+<span id="errorMsg"></span><br>
149
+<iframe id="iframe_a" name="iframe_a"></iframe>
133 150
 </div>
134
-
135
-<div class="chartContainer">
136
-<img class="chart" id="signalChart">
137 151
 </div>
138 152
 
139
-<div class="chartContainer">
140
-<img class="chart" id="noiseChart">
141
-</div>
153
+<br>
142 154
 
155
+<div class="rowContainer" id="stockChartsContainer">
143 156
 <div class="chartContainer">
144
-<img class="chart" id="rxrateChart">
157
+<img class="chart" id="signalChart">
145 158
 </div>
146
-
147 159
 <div class="chartContainer">
148
-<img class="chart" id="txrateChart">
160
+<img class="chart" id="snrChart">
161
+</div>
149 162
 </div>
150 163
 
151 164
 <div class="notes">
... ...
@@ -175,25 +188,32 @@ var nodeDataUrl = "dynamic/nodeOnline.js";
175 188
 /* Global DOM objects */
176 189
 
177 190
 // Chart elements
178
-var snrChart = document.getElementById("snrChart");
179 191
 var signalChart = document.getElementById("signalChart");
180
-var noiseChart = document.getElementById("noiseChart");
181
-var rxrateChart = document.getElementById("rxrateChart");
182
-var txrateChart = document.getElementById("txrateChart");
192
+var snrChart = document.getElementById("snrChart");
183 193
 
184 194
 // Text elements
185 195
 var dateElmt = document.getElementById("date");    
186 196
 var timeElmt = document.getElementById("time"); 
187 197
 var statusElmt = document.getElementById("status");
188 198
 var periodElmt = document.getElementById("period");
199
+
200
+// Document elements
201
+var customChartsContainer = document.getElementById("customChartsContainer");
202
+var stockChartsContainer = document.getElementById("stockChartsContainer");
203
+var chartContainer = document.getElementsByClassName("chartContainer");
204
+var fmDateSelector = document.getElementById("fmDateSelector");
205
+var beginDate = document.getElementById("beginDate");
206
+var endDate = document.getElementById("endDate");
207
+var errorMsg = document.getElementById("errorMsg");
208
+
189 209
 /* Global objects */
190 210
 
191 211
 var httpRequest = new XMLHttpRequest();
192 212
 
193 213
 /* Global variables */
194 214
 
195
-var graphPeriod;
196
-var graphRefreshRate = 10; // chart refresh rate in minutes
215
+var chartPeriod = 1;
216
+var chartRefreshRate = 10; // chart refresh rate in minutes
197 217
 
198 218
 function main() {
199 219
     /* Register call back function to process http requests */
... ...
@@ -209,9 +229,9 @@ function main() {
209 229
         displayOfflineStatus();
210 230
     };
211 231
 
232
+    initializeDateSelector();
212 233
     getNodeData();
213
-    graphPeriod = 1;
214
-    getNodeGraphs();
234
+    getNodeCharts();
215 235
 }
216 236
 
217 237
 function getNodeData() {
... ...
@@ -221,15 +241,22 @@ function getNodeData() {
221 241
 }
222 242
 
223 243
 function setChartPeriod(n) {
224
-    graphPeriod = n;
225
-    getNodeGraphs();   
244
+    chartPeriod = n;
245
+    if (n == 0) {
246
+        customChartsContainer.style.display = "block";
247
+        stockChartsContainer.style.display = "none";
248
+    } else {
249
+        customChartsContainer.style.display = "none";
250
+        stockChartsContainer.style.display = "block";
251
+    getNodeCharts();   
252
+    }
226 253
 }
227 254
 
228
-function getNodeGraphs() {
255
+function getNodeCharts() {
229 256
     var d = new Date;
230 257
     var pfx;
231 258
 
232
-    switch(graphPeriod) {
259
+    switch(chartPeriod) {
233 260
         case 1:
234 261
             pfx = "24hr_";
235 262
             break;
... ...
@@ -241,10 +268,7 @@ function getNodeGraphs() {
241 268
             break;
242 269
     }
243 270
     signalChart.src = "dynamic/" + pfx + "signal.png?ver=" + d.getTime();
244
-    noiseChart.src = "dynamic/" + pfx + "noise.png?ver=" + d.getTime();
245 271
     snrChart.src = "dynamic/" + pfx + "snr.png?ver=" + d.getTime();
246
-    rxrateChart.src = "dynamic/" + pfx + "rx_rate.png?ver=" + d.getTime();
247
-    txrateChart.src = "dynamic/" + pfx + "tx_rate.png?ver=" + d.getTime();
248 272
 }
249 273
 
250 274
 function displayData(dataItem) {
... ...
@@ -259,29 +283,51 @@ function displayData(dataItem) {
259 283
     localTimeZone = localDate.getTimezoneOffset() / 60;
260 284
     dateElmt.innerHTML = date;    
261 285
     timeElmt.innerHTML = hourminute +
262
-        "  <small>(GMT+" + localTimeZone + ")</small>";    
286
+                         "  <small>(GMT+" + localTimeZone + ")</small>";    
263 287
      
264 288
     statusElmt.innerHTML = "Online";
265 289
     statusElmt.style.color = "green";
266 290
 
267
-    graphRefreshRate = dataItem.period;
268
-    periodElmt.innerHTML = graphRefreshRate;
269
-    setInterval(getNodeData, 60000 * graphRefreshRate);
270
-    setInterval(getNodeGraphs, 60000 * graphRefreshRate);
291
+    chartRefreshRate = dataItem.period;
292
+    periodElmt.innerHTML = chartRefreshRate;
293
+    setInterval(getNodeData, 60000 * chartRefreshRate);
294
+    setInterval(getNodeCharts, 60000 * chartRefreshRate);
271 295
 }
272 296
 
273 297
 function displayOfflineStatus() {
274 298
     var d = new Date();
275 299
     localTimeZone = d.getTimezoneOffset() / 60;
276 300
     dateElmt.innerHTML = (d.getMonth() + 1) + "/" + d.getDate() + "/" +
277
-                       d.getFullYear();    
301
+                          d.getFullYear();    
278 302
     timeElmt.innerHTML = d.getHours() + ":" + d.getMinutes() +
279
-                       "  <small>(GMT+" + localTimeZone + ")</small>";
303
+                         "  <small>(GMT+" + localTimeZone + ")</small>";
280 304
     periodElmt.innerHTML = "?";    
281 305
     statusElmt.innerHTML = "offline";    
282 306
     statusElmt.style.color = "red";
283 307
 }
284 308
 
309
+function initializeDateSelector() {
310
+    var d = new Date();
311
+
312
+    var dEnd = new Date(d.getFullYear(),
313
+               d.getMonth(), d.getDate() - 0);
314
+
315
+    var dBegin = new Date(d.getFullYear(),
316
+               d.getMonth(), d.getDate() - 1);
317
+
318
+    var strBegin = dBegin.getFullYear() + "-" + (dBegin.getMonth() + 1) + 
319
+                   "-" + dBegin.getDate();
320
+    var strEnd = dEnd.getFullYear() + "-" + (dEnd.getMonth() + 1) + 
321
+                 "-" + dEnd.getDate();
322
+
323
+    beginDate.valueAsDate = dBegin;
324
+    endDate.valueAsDate = dEnd;
325
+    //errorMsg.innerHTML = strBegin + " thru " + strEnd;
326
+}
327
+
328
+function getCustomCharts() {
329
+    fmDateSelector.submit();
330
+}
285 331
 </script>
286 332
 
287 333
 </body>