changeset 13:a0213f0e707b

- Update for v12.10 - Plot raw data to show more up to date data - Average/peak winds so they don't spam the display. - Display the time of the most recent data.
author Daniel O'Connor <darius@dons.net.au>
date Thu, 10 Jan 2013 16:38:36 +1030
parents bfe6ed563ffc
children 294e6a7fbb6e
files iwws.py static/iwws.css static/iwws.js static/sprintf.js templates/index.html
diffstat 5 files changed, 221 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/iwws.py	Wed Jun 20 12:57:30 2012 +0930
+++ b/iwws.py	Thu Jan 10 16:38:36 2013 +1030
@@ -34,7 +34,7 @@
 
 import calendar
 import datetime
-import DataStore
+import pywws.DataStore
 import flask
 import logging
 
@@ -58,22 +58,39 @@
 @app.route('/getdata.json')
 def getdata():
     app.logger.debug("Getting data")
-    store = DataStore.hourly_store(DATA_DIR)
+    if 'duration' in flask.request.args:
+        duration = float(flask.request.args['duration'])
+    else:
+        duration = 24 * 60 * 60
+    duration = datetime.timedelta(seconds = duration)
+    store = pywws.DataStore.data_store(DATA_DIR)
     now = datetime.datetime.now()
-    then = now - datetime.timedelta(1)
+    then = now - duration
     data = store[then:now]
     app.logger.debug("done")
     r = {}
     for k in store.key_list:
         r[k] = []
 
+    # Raw data has absolute rain counts
+    baserain = None
     for d in data:
+        if baserain == None:
+            baserain = d['rain']
+        delta = d['rain'] - baserain
+        # Sanity check
+        if d['rain'] < baserain or delta > 100:
+            delta = 0
+        d['rain'] = delta
+
+        lastdata = d['idx']
         for k in d.keys():
             if k == 'idx':
                 t = int(dt2epoch(d[k]))
                 r[k].append(t)
             else:
                 r[k].append(d[k])
+        r['lastdata'] = dt2epoch(lastdata)
     return flask.jsonify(r)
 
 def dt2epoch(dt):
--- a/static/iwws.css	Wed Jun 20 12:57:30 2012 +0930
+++ b/static/iwws.css	Thu Jan 10 16:38:36 2013 +1030
@@ -11,16 +11,23 @@
     font-size: x-small;
 }
 
+
+div#stamp {
+    font-size: small;
+    margin-left : auto;
+    margin-right : auto;
+    height: 4%;
+}
 div#graph1 {
     margin-left : auto;
     margin-right : auto;
     width: 97%;
-    height: 45%;
+    height: 43%;
 }
 
 div#graph2 {
     margin-left : auto;
     margin-right : auto;
     width: 97%;
-    height: 45%;
+    height: 43%;
 }
--- a/static/iwws.js	Wed Jun 20 12:57:30 2012 +0930
+++ b/static/iwws.js	Thu Jan 10 16:38:36 2013 +1030
@@ -45,28 +45,71 @@
     var l = datacache['idx'].length - 1;
     var d = new Date();
     var tzofs = d.getTimezoneOffset() * 60;
+    var wavgtmp = 0;
+    var wgusttmp = 0;
+    var winddir = 0;
+    var windavgs = 0;
+    var windnavg = 4;
     for (i = 0; i < l; i++) {
-	// Convert time from UTC to browser LT
-	t = datacache['idx'][i] - tzofs;
+	// Apply offset so Flot times look right (it always shows UTC)
+	t = (datacache['idx'][i] - tzofs) * 1000.0;
+
+	// Keep track of min/max temperature
 	if (datacache['temp_out'][i] < mint)
 	    mint = datacache['temp_out']
 	if (datacache['temp_out'][i] > maxt)
 	    maxt = datacache['temp_out'][i]
-	temp_out.push([t * 1000.0, datacache['temp_out'][i]]);
-	if (datacache['hum_out'][i] != null && datacache['temp_out'][i] != null) {
-	    dewpt.push([t * 1000.0, hum2dp(datacache['hum_out'][i], datacache['temp_out'][i])]);
+
+	// Store outside temperature if valid
+	if (datacache['temp_out'][i] != null) {
+	    temp_out.push([t, datacache['temp_out'][i]]);
+
+	    // Calculate dewpoint if outside humidity is valid
+	    if (datacache['hum_out'][i] != null) {
+		dewpt.push([t, hum2dp(datacache['hum_out'][i], datacache['temp_out'][i])]);
+	    }
 	}
-	wavg.push([t * 1000.0, datacache['wind_ave'][i] * 60.0 * 60.0 / 1000.0, wind2angle(datacache['wind_dir'][i])]);
-	wgust.push([t * 1000.0, datacache['wind_gust'][i] * 60.0 * 60.0 / 1000.0, wind2angle(datacache['wind_dir'][i])]);
-	pressure.push([t * 1000.0, datacache['abs_pressure'][i]]);
+
+	// Store baro
+	pressure.push([t, datacache['abs_pressure'][i]]);
+
+	// Store rain if there was any
 	if (datacache['rain'][i] > 0)
-	    rain.push([t * 1000.0, datacache['rain'][i]]);
+	    rain.push([t, datacache['rain'][i]]);
+
+	// Store wind (convert to km/r)
+	// Average the average wind speed
+	wavgtmp += datacache['wind_ave'][i] * 60.0 * 60.0 / 1000.0
+	// Pick highest speed gusts
+	var g = datacache['wind_gust'][i] * 60.0 * 60.0 / 1000.0;
+	if (g > wgusttmp) {
+	    wgusttmp = g;
+	}
+	// Accumulate speed/gust direction
+	winddir += wind2angle(datacache['wind_dir'][i]);
+	windavgs++;
+	// Normalise
+	if (windavgs == windnavg) {
+	    windavgs = 0;
+	    wavgtmp /= windnavg;
+	    // Note: gust is peak, so nothing to do
+	    winddir /= windnavg;
+
+	    // Store
+	    wavg.push([t, wavgtmp, winddir]);
+	    wgust.push([t, wgusttmp, winddir]);
+
+	    // Reset accumulators
+	    wavgtmp = 0;
+	    wgusttmp = 0;
+	    winddir = 0;
+	}
     }
 
     $.plot($("#graph1"), [
-	{ data : pressure, label: "Baro", yaxis : 1, points : { show : true }, lines : { show : true } },
-	{ data : temp_out, label: "Temp.", yaxis : 2, points : { show : true }, lines : { show : true } },
-	{ data : dewpt, label: "Dew point", yaxis : 2, points : { show : true }, lines : { show : true } },
+	{ data : pressure, label: "Baro", yaxis : 1, points : { show : true, radius : 0 }, lines : { show : true } },
+	{ data : temp_out, label: "Temp.", yaxis : 2, points : { show : true, radius : 0 }, lines : { show : true } },
+	{ data : dewpt, label: "Dew point", yaxis : 2, points : { show : true, radius : 0 }, lines : { show : true } },
     ],
 	   { xaxis : { mode : 'time' },
 	     legend : { backgroundOpacity : 0, position : 'nw' },
@@ -75,7 +118,7 @@
 	   });
     $.plot($("#graph2"), [
 	{ data : wavg, label: "Wind (Avg)", yaxis : 1, points : { show : true }, lines : { show : true }, direction : true },
-	{ data : wgust, label: "Wind (Gust)", yaxis : 1, points : { show : true }, lines : { show : true }, direction : true },
+	{ data : wgust, label: "Wind (Gust)", yaxis : 1, points : { show : true }, lines : { show : true }, direction :  true },
 	{ data : rain, label: "Rain", yaxis : 2, bars : { show : true, barWidth : 0.5 * 60 * 60 * 1000, align : "centre" } },
     ],
 	   { xaxis : { mode : 'time' },
@@ -83,6 +126,10 @@
 	     yaxis : { tickFormatter : spdFormatter },
 	     y2axis : { min : 0, tickFormatter : mmFormatter }
 	   });
+    // Reverse tzofs calculation as Date does local time
+    var dobj = new Date(datacache['lastdata'] * 1000.0);
+    $('#stamp').html(sprintf("Last data: %02d/%02d/%02d %02d:%02d:%02d", dobj.getFullYear(), dobj.getMonth() + 1, dobj.getDate(),
+			     dobj.getHours(), dobj.getMinutes(), dobj.getSeconds()));
 }
 
 function wind2angle(dir) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static/sprintf.js	Thu Jan 10 16:38:36 2013 +1030
@@ -0,0 +1,130 @@
+/**
+ *
+ *  Javascript sprintf
+ *  http://www.webtoolkit.info/
+ *
+ *
+ **/
+
+sprintfWrapper = {
+    init : function () {
+	if (typeof arguments == "undefined") { return null; }
+	if (arguments.length < 1) { return null; }
+	if (typeof arguments[0] != "string") { return null; }
+	if (typeof RegExp == "undefined") { return null; }
+	
+	var string = arguments[0];
+	var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
+	var matches = new Array();
+	var strings = new Array();
+	var convCount = 0;
+	var stringPosStart = 0;
+	var stringPosEnd = 0;
+	var matchPosEnd = 0;
+	var newString = '';
+	var match = null;
+	
+	while (match = exp.exec(string)) {
+	    if (match[9]) { convCount += 1; }
+	    
+	    stringPosStart = matchPosEnd;
+	    stringPosEnd = exp.lastIndex - match[0].length;
+	    strings[strings.length] = string.substring(stringPosStart, stringPosEnd);
+	    
+	    matchPosEnd = exp.lastIndex;
+	    matches[matches.length] = {
+		match: match[0],
+		left: match[3] ? true : false,
+		sign: match[4] || '',
+		pad: match[5] || ' ',
+		min: match[6] || 0,
+		precision: match[8],
+		code: match[9] || '%',
+		negative: parseInt(arguments[convCount]) < 0 ? true : false,
+		argument: String(arguments[convCount])
+	    };
+	}
+	strings[strings.length] = string.substring(matchPosEnd);
+	
+	if (matches.length == 0) { return string; }
+	if ((arguments.length - 1) < convCount) { return null; }
+	
+	var code = null;
+	var match = null;
+	var i = null;
+	
+	for (i=0; i<matches.length; i++) {
+	    
+	    if (matches[i].code == '%') { substitution = '%' }
+	    else if (matches[i].code == 'b') {
+		matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(2));
+		substitution = sprintfWrapper.convert(matches[i], true);
+	    }
+	    else if (matches[i].code == 'c') {
+		matches[i].argument = String(String.fromCharCode(parseInt(Math.abs(parseInt(matches[i].argument)))));
+		substitution = sprintfWrapper.convert(matches[i], true);
+	    }
+	    else if (matches[i].code == 'd') {
+		matches[i].argument = String(Math.abs(parseInt(matches[i].argument)));
+		substitution = sprintfWrapper.convert(matches[i]);
+	    }
+	    else if (matches[i].code == 'f') {
+		matches[i].argument = String(Math.abs(parseFloat(matches[i].argument)).toFixed(matches[i].precision ? matches[i].precision : 6));
+		substitution = sprintfWrapper.convert(matches[i]);
+	    }
+	    else if (matches[i].code == 'o') {
+		matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(8));
+		substitution = sprintfWrapper.convert(matches[i]);
+	    }
+	    else if (matches[i].code == 's') {
+		matches[i].argument = matches[i].argument.substring(0, matches[i].precision ? matches[i].precision : matches[i].argument.length)
+		substitution = sprintfWrapper.convert(matches[i], true);
+	    }
+	    else if (matches[i].code == 'x') {
+		matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
+		substitution = sprintfWrapper.convert(matches[i]);
+	    }
+	    else if (matches[i].code == 'X') {
+		matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
+		substitution = sprintfWrapper.convert(matches[i]).toUpperCase();
+	    }
+	    else {
+		substitution = matches[i].match;
+	    }
+	    
+	    newString += strings[i];
+	    newString += substitution;
+	    
+	}
+	newString += strings[i];
+	
+	return newString;
+	
+    },
+    
+    convert : function(match, nosign){
+	if (nosign) {
+	    match.sign = '';
+	} else {
+	    match.sign = match.negative ? '-' : match.sign;
+	}
+	var l = match.min - match.argument.length + 1 - match.sign.length;
+	var pad = new Array(l < 0 ? 0 : l).join(match.pad);
+	if (!match.left) {
+	    if (match.pad == "0" || nosign) {
+		return match.sign + pad + match.argument;
+	    } else {
+		return pad + match.sign + match.argument;
+	    }
+	} else {
+	    if (match.pad == "0" || nosign) {
+		return match.sign + match.argument + pad.replace(/0/g, ' ');
+	    } else {
+		return match.sign + match.argument + pad;
+	    }
+	}
+    }
+}
+
+sprintf = sprintfWrapper.init;
+
--- a/templates/index.html	Wed Jun 20 12:57:30 2012 +0930
+++ b/templates/index.html	Thu Jan 10 16:38:36 2013 +1030
@@ -12,6 +12,7 @@
     <script type="text/javascript" src="static/jquery.log.js"></script> 
     <script type="text/javascript" src="static/jquery.flot.js"></script> 
     <script type="text/javascript" src="static/jquery.flot.direction.js"></script> 
+    <script type="text/javascript" src="static/sprintf.js"></script> 
     <script src="static/iwws.js" type="application/x-javascript" charset="utf-8"></script>
   </head>
   <body>
@@ -22,6 +23,7 @@
 	  <a href="#home" onclick="update_data()" class="button leftButton slideleft submit">Update</a>
 	</div>
 	<div id="container">
+	  <div id="stamp">&nbsp</div>
 	  <div id="graph1"></div> 
 	  <div id="graph2"></div> 
 	</div>