From 47326ed774b2bc71f8abc18e10b911e296c48629 Mon Sep 17 00:00:00 2001 From: Milan Toman Date: Wed, 21 Aug 2019 15:44:49 +0200 Subject: [PATCH] New, updated version of web. Might contain customizations --- web/chttpd.py | 84 ++++---- web/static/js/dygraph.js | 189 ++++++++++++----- web/static/js/solar_graph.js | 126 ++++++++---- web/static/js/weather_graph.js | 260 +++++++++++++++++------- web/templates/footer.html | 4 +- web/templates/header.html | 33 ++- web/templates/status_admin.html | 56 +++++- web/templates/top_menu.html | 5 +- web/templates/voltage_admin.html | 31 +-- web/templates/weather_admin.html | 68 +++++-- web/templates/weather_admin.html.orig | 280 -------------------------- 11 files changed, 600 insertions(+), 536 deletions(-) mode change 100644 => 100755 web/templates/status_admin.html delete mode 100644 web/templates/weather_admin.html.orig diff --git a/web/chttpd.py b/web/chttpd.py index 44da264..ee303a9 100755 --- a/web/chttpd.py +++ b/web/chttpd.py @@ -1,59 +1,65 @@ #!/usr/bin/python3 -"""Our cherrypy server, it all starts here -Let's look at the statically linked stuff as well (i.e. the files that will -serve us data). These imports are static files in the same directory. Each -a spearate application with it's own face and behavior -""" import os import cherrypy -import sys -# UI for weather ./weather.py -import weather -# CSV "API" for graphs, or individual pulling ./dynamic.py -import dynamic -# UI for RasPi / davis status ./status.py -import status -PATH = os.path.abspath(os.path.dirname(__file__)) +import index +from modules import voltage +from modules import weather +from modules import dynamic +from modules import status +from modules import temphumi +import config -# Where are we? -_SCRIPT_PATH = os.path.dirname(sys.argv[0]) -# Certificates fro SSL reside here -_CERT_PATH = _SCRIPT_PATH + '/.cert' -# So we can load static libraries -# sys.path.append(_SCRIPT_PATH) +def main_server_loop(): + ''' Master http server - the main executable / daemon + + Contains basic server settings and how the sub-modules + are called and mounted to their respective paths -# basic config for the server, the SSL part is questionable... -server_config = { - 'server.socket_host': '0.0.0.0', - 'server.socket_port': 80 -} -# commit the config settings -cherrypy.config.update(server_config) + Args: + *None* -# If launched directly, let's go -if __name__ == '__main__': + Sets: + *server_config:* dict(), updates cherrypy.config + *conf:* dict(), see Cherrypy docs for more + *cherrypy.config:* dict(), see Cherrypy docs for more + + Returns: + *N/A* + + Raises: + *Exception* If server is unable to start + + ''' + server_config={ + 'server.socket_host': config.Conf.val['_server_bind_ip'], + 'server.socket_port': config.Conf.val['_server_port'] + } + cherrypy.config.update(server_config) conf = { '/': { 'tools.sessions.on': True, - 'tools.staticdir.root': os.path.abspath(_SCRIPT_PATH + '/') + 'tools.staticdir.root': os.path.abspath(config.SCRIPT_PATH + '/') }, '/static': { 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.abspath(_SCRIPT_PATH + '/static') - }, - '/data': { - 'tools.staticdir.on': False, - 'tools.staticdir.dir': os.path.abspath(_SCRIPT_PATH + '/dynamic') + 'tools.staticdir.dir': './static' } } - # Here are the different served mounts - cherrypy.tree.mount(weather.WeatherInfo(), "/", conf) + cherrypy.tree.mount(voltage.EnergyInfo(), "/", conf) + cherrypy.tree.mount(voltage.EnergyInfo(), "/energy", conf) + cherrypy.tree.mount(weather.WeatherInfo(), "/weather", conf) cherrypy.tree.mount(status.StatusInfo(), "/status", conf) - cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf) - - # Run the server, lock it in place. + cherrypy.tree.mount(dynamic.Expose(), "/data", conf) + cherrypy.tree.mount(temphumi.PuerhInfo(), "/temphumi", conf) cherrypy.engine.start() cherrypy.engine.block() + + +if __name__ == '__main__': + try: + main_server_loop() + except Exception as e: + raise e diff --git a/web/static/js/dygraph.js b/web/static/js/dygraph.js index 012faa1..dff393a 100644 --- a/web/static/js/dygraph.js +++ b/web/static/js/dygraph.js @@ -10,35 +10,83 @@ var process = module.exports = {}; var cachedSetTimeout; var cachedClearTimeout; +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} (function () { try { - cachedSetTimeout = setTimeout; - } catch (e) { - cachedSetTimeout = function () { - throw new Error('setTimeout is not defined'); + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; } + } catch (e) { + cachedSetTimeout = defaultSetTimout; } try { - cachedClearTimeout = clearTimeout; - } catch (e) { - cachedClearTimeout = function () { - throw new Error('clearTimeout is not defined'); + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; } } ()) function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations return setTimeout(fun, 0); - } else { - return cachedSetTimeout.call(null, fun, 0); } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { - clearTimeout(marker); - } else { - cachedClearTimeout.call(null, marker); + //normal enviroments in sane situations + return clearTimeout(marker); } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + } var queue = []; var draining = false; @@ -121,6 +169,10 @@ process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); @@ -355,7 +407,7 @@ module.exports = exports["default"]; */ /** - * @fileoverview DataHandler implementation for the combination + * @fileoverview DataHandler implementation for the combination * of error bars and fractions options. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ @@ -475,7 +527,7 @@ module.exports = exports["default"]; */ /** - * @fileoverview DataHandler base implementation for the "bar" + * @fileoverview DataHandler base implementation for the "bar" * data formats. This implementation must be extended and the * extractSeries and rollingAverage must be implemented. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) @@ -512,13 +564,13 @@ BarsHandler.prototype = new _datahandler2['default'](); // (I get closure compiler errors if this isn't here.) /** * @override - * @param {!Array.} rawData The raw data passed into dygraphs where + * @param {!Array.} rawData The raw data passed into dygraphs where * rawData[i] = [x,ySeries1,...,ySeriesN]. * @param {!number} seriesIndex Index of the series to extract. All other * series should be ignored. * @param {!DygraphOptions} options Dygraph options. * @return {Array.<[!number,?number,?]>} The series in the unified data format - * where series[i] = [x,y,{extras}]. + * where series[i] = [x,y,{extras}]. */ BarsHandler.prototype.extractSeries = function (rawData, seriesIndex, options) { // Not implemented here must be extended @@ -526,7 +578,7 @@ BarsHandler.prototype.extractSeries = function (rawData, seriesIndex, options) { /** * @override - * @param {!Array.<[!number,?number,?]>} series The series in the unified + * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x,y,{extras}]. * @param {!number} rollPeriod The number of points over which to average the data * @param {!DygraphOptions} options The dygraph options. @@ -2789,7 +2841,8 @@ DygraphInteraction.defaultModel = { // Give plugins a chance to grab this event. var e = { canvasx: context.dragEndX, - canvasy: context.dragEndY + canvasy: context.dragEndY, + cancelable: true }; if (g.cascadeEvents_('dblclick', e)) { return; @@ -3447,6 +3500,12 @@ if (typeof process !== 'undefined') { "type": "integer", "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." }, + "pixelRatio": { + "default": "(devicePixelRatio / context.backingStoreRatio)", + "labels": ["Overall display"], + "type": "float", + "description": "Overrides the pixel ratio scaling factor for the canvas's 2d context. Ordinarily, this is set to the devicePixelRatio / (context.backingStoreRatio || 1), so on mobile devices, where the devicePixelRatio can be somewhere around 3, performance can be improved by overriding this value to something less precise, like 1, at the expense of resolution." + }, "interactionModel": { "default": "...", "labels": ["Interactive Elements"], @@ -3716,7 +3775,7 @@ if (typeof process !== 'undefined') { "default": "null", "labels": ["Axis display", "Interactive Elements"], "type": "float", - "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds." + "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% passed the edges of the displayed values. null means no bounds." }, "title": { "labels": ["Chart labels"], @@ -4646,29 +4705,36 @@ var dateTicker = function dateTicker(a, b, pixels, opts, dygraph, vals) { exports.dateTicker = dateTicker; // Time granularity enumeration var Granularity = { - SECONDLY: 0, - TWO_SECONDLY: 1, - FIVE_SECONDLY: 2, - TEN_SECONDLY: 3, - THIRTY_SECONDLY: 4, - MINUTELY: 5, - TWO_MINUTELY: 6, - FIVE_MINUTELY: 7, - TEN_MINUTELY: 8, - THIRTY_MINUTELY: 9, - HOURLY: 10, - TWO_HOURLY: 11, - SIX_HOURLY: 12, - DAILY: 13, - TWO_DAILY: 14, - WEEKLY: 15, - MONTHLY: 16, - QUARTERLY: 17, - BIANNUAL: 18, - ANNUAL: 19, - DECADAL: 20, - CENTENNIAL: 21, - NUM_GRANULARITIES: 22 + MILLISECONDLY: 0, + TWO_MILLISECONDLY: 1, + FIVE_MILLISECONDLY: 2, + TEN_MILLISECONDLY: 3, + FIFTY_MILLISECONDLY: 4, + HUNDRED_MILLISECONDLY: 5, + FIVE_HUNDRED_MILLISECONDLY: 6, + SECONDLY: 7, + TWO_SECONDLY: 8, + FIVE_SECONDLY: 9, + TEN_SECONDLY: 10, + THIRTY_SECONDLY: 11, + MINUTELY: 12, + TWO_MINUTELY: 13, + FIVE_MINUTELY: 14, + TEN_MINUTELY: 15, + THIRTY_MINUTELY: 16, + HOURLY: 17, + TWO_HOURLY: 18, + SIX_HOURLY: 19, + DAILY: 20, + TWO_DAILY: 21, + WEEKLY: 22, + MONTHLY: 23, + QUARTERLY: 24, + BIANNUAL: 25, + ANNUAL: 26, + DECADAL: 27, + CENTENNIAL: 28, + NUM_GRANULARITIES: 29 }; exports.Granularity = Granularity; @@ -4699,6 +4765,13 @@ var DateField = { * @type {Array.<{datefield:number, step:number, spacing:number}>} */ var TICK_PLACEMENT = []; +TICK_PLACEMENT[Granularity.MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1 }; +TICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2 }; +TICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5 }; +TICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10 }; +TICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50 }; +TICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100 }; +TICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500 }; TICK_PLACEMENT[Granularity.SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1 }; TICK_PLACEMENT[Granularity.TWO_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2 }; TICK_PLACEMENT[Granularity.FIVE_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5 }; @@ -4963,7 +5036,7 @@ var logRangeFraction = function logRangeFraction(r0, r1, pct) { // Original calcuation: // pct = (log(x) - log(xRange[0])) / (log(xRange[1]) - log(xRange[0]))); // - // Multiply both sides by the right-side demoninator. + // Multiply both sides by the right-side denominator. // pct * (log(xRange[1] - log(xRange[0]))) = log(x) - log(xRange[0]) // // add log(xRange[0]) to both sides @@ -5231,7 +5304,7 @@ function isValidPoint(p, opt_allowNaNY) { ; /** - * Number formatting function which mimicks the behavior of %g in printf, i.e. + * Number formatting function which mimics the behavior of %g in printf, i.e. * either exponential or fixed format (without trailing 0s) is used depending on * the length of the generated string. The advantage of this format is that * there is a predictable upper bound on the resulting string length, @@ -5385,7 +5458,7 @@ function hmsString_(hh, mm, ss, ms) { /** * Convert a JS date (millis since epoch) to a formatted string. * @param {number} time The JavaScript time value (ms since epoch) - * @param {boolean} utc Wether output UTC or local time + * @param {boolean} utc Whether output UTC or local time * @return {string} A date of one of these forms: * "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS" * @private @@ -6293,6 +6366,12 @@ function dateAxisLabelFormatter(date, granularity, opts) { if (frac === 0 || granularity >= DygraphTickers.Granularity.DAILY) { // e.g. '21 Jan' (%d%b) return zeropad(day) + ' ' + SHORT_MONTH_NAMES_[month]; + } else if (granularity < DygraphTickers.Granularity.SECONDLY) { + // e.g. 40.310 (meaning 40 seconds and 310 milliseconds) + var str = "" + millis; + return zeropad(secs) + "." + ('000' + str).substring(str.length); + } else if (granularity > DygraphTickers.Granularity.MINUTELY) { + return hmsString_(hours, mins, secs, 0); } else { return hmsString_(hours, mins, secs, millis); } @@ -6372,7 +6451,7 @@ function dateValueFormatter(d, opts) { * @param {Object} attrs Various other attributes, e.g. errorBars determines * whether the input data contains error ranges. For a complete list of * options, see http://dygraphs.com/options.html. - */var Dygraph=function Dygraph(div,data,opts){this.__init__(div,data,opts);};Dygraph.NAME = "Dygraph";Dygraph.VERSION = "2.0.0"; // Various default values + */var Dygraph=function Dygraph(div,data,opts){this.__init__(div,data,opts);};Dygraph.NAME = "Dygraph";Dygraph.VERSION = "2.1.0"; // Various default values Dygraph.DEFAULT_ROLL_PERIOD = 1;Dygraph.DEFAULT_WIDTH = 480;Dygraph.DEFAULT_HEIGHT = 320; // For max 60 Hz. animation: Dygraph.ANIMATION_STEPS = 12;Dygraph.ANIMATION_DURATION = 200; /** * Standard plotters. These may be used by clients. @@ -6643,9 +6722,9 @@ var target=e.target || e.fromElement;var relatedTarget=e.relatedTarget || e.toEl // This happens when the graph is resized. if(!this.resizeHandler_){this.resizeHandler_ = function(e){dygraph.resize();}; // Update when the window is resized. // TODO(danvk): drop frames depending on complexity of the chart. -this.addAndTrackEvent(window,'resize',this.resizeHandler_);}};Dygraph.prototype.resizeElements_ = function(){this.graphDiv.style.width = this.width_ + "px";this.graphDiv.style.height = this.height_ + "px";var canvasScale=utils.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width = this.width_ * canvasScale;this.canvas_.height = this.height_ * canvasScale;this.canvas_.style.width = this.width_ + "px"; // for IE +this.addAndTrackEvent(window,'resize',this.resizeHandler_);}};Dygraph.prototype.resizeElements_ = function(){this.graphDiv.style.width = this.width_ + "px";this.graphDiv.style.height = this.height_ + "px";var pixelRatioOption=this.getNumericOption('pixelRatio');var canvasScale=pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width = this.width_ * canvasScale;this.canvas_.height = this.height_ * canvasScale;this.canvas_.style.width = this.width_ + "px"; // for IE this.canvas_.style.height = this.height_ + "px"; // for IE -if(canvasScale !== 1){this.canvas_ctx_.scale(canvasScale,canvasScale);}var hiddenScale=utils.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width = this.width_ * hiddenScale;this.hidden_.height = this.height_ * hiddenScale;this.hidden_.style.width = this.width_ + "px"; // for IE +if(canvasScale !== 1){this.canvas_ctx_.scale(canvasScale,canvasScale);}var hiddenScale=pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width = this.width_ * hiddenScale;this.hidden_.height = this.height_ * hiddenScale;this.hidden_.style.width = this.width_ + "px"; // for IE this.hidden_.style.height = this.height_ + "px"; // for IE if(hiddenScale !== 1){this.hidden_ctx_.scale(hiddenScale,hiddenScale);}}; /** * Detach DOM elements in the dygraph and null out all data references. @@ -6801,6 +6880,7 @@ var oldValueRanges=this.yAxisRanges();var newValueRanges=[];for(var i=0;i < this */Dygraph.prototype.resetZoom = function(){var _this4=this;var dirtyX=this.isZoomed('x');var dirtyY=this.isZoomed('y');var dirty=dirtyX || dirtyY; // Clear any selection, since it's likely to be drawn in the wrong place. this.clearSelection();if(!dirty)return; // Calculate extremes to avoid lack of padding on reset. var _xAxisExtremes=this.xAxisExtremes();var _xAxisExtremes2=_slicedToArray(_xAxisExtremes,2);var minDate=_xAxisExtremes2[0];var maxDate=_xAxisExtremes2[1];var animatedZooms=this.getBooleanOption('animatedZooms');var zoomCallback=this.getFunctionOption('zoomCallback'); // TODO(danvk): merge this block w/ the code below. +// TODO(danvk): factor out a generic, public zoomTo method. if(!animatedZooms){this.dateWindow_ = null;this.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});this.drawGraph_();if(zoomCallback){zoomCallback.call(this,minDate,maxDate,this.yAxisRanges());}return;}var oldWindow=null,newWindow=null,oldValueRanges=null,newValueRanges=null;if(dirtyX){oldWindow = this.xAxisRange();newWindow = [minDate,maxDate];}if(dirtyY){oldValueRanges = this.yAxisRanges();newValueRanges = this.yAxisExtremes();}this.doAnimatedZoom(oldWindow,newWindow,oldValueRanges,newValueRanges,function(){_this4.dateWindow_ = null;_this4.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});if(zoomCallback){zoomCallback.call(_this4,minDate,maxDate,_this4.yAxisRanges());}});}; /** * Combined animation logic for all zoom functions. * either the x parameters or y parameters may be null. @@ -7217,7 +7297,7 @@ if('rollPeriod' in attrs){this.rollPeriod_ = attrs.rollPeriod;}if('dateWindow' i // highlightCircleSize // Check if this set options will require new points. var requiresNewPoints=utils.isPixelChangingOptionList(this.attr_("labels"),attrs);utils.updateDeep(this.user_attrs_,attrs);this.attributes_.reparseSeries();if(file){ // This event indicates that the data is about to change, but hasn't yet. -// TODO(danvk): support cancelation of the update via this event. +// TODO(danvk): support cancellation of the update via this event. this.cascadeEvents_('dataWillUpdate',{});this.file_ = file;if(!block_redraw)this.start_();}else {if(!block_redraw){if(requiresNewPoints){this.predraw_();}else {this.renderGraph_(false);}}}}; /** * Make a copy of input attributes, removing file as a convenience. * @private @@ -8748,8 +8828,8 @@ rangeSelector.prototype.updateVisibility_ = function () { * Resizes the range selector. */ rangeSelector.prototype.resize_ = function () { - function setElementRect(canvas, context, rect) { - var canvasScale = utils.getContextPixelRatio(context); + function setElementRect(canvas, context, rect, pixelRatioOption) { + var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context); canvas.style.top = rect.y + 'px'; canvas.style.left = rect.x + 'px'; @@ -8776,8 +8856,9 @@ rangeSelector.prototype.resize_ = function () { h: this.getOption_('rangeSelectorHeight') }; - setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_); - setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_); + var pixelRatioOption = this.dygraph_.getNumericOption('pixelRatio'); + setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_, pixelRatioOption); + setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_, pixelRatioOption); }; /** diff --git a/web/static/js/solar_graph.js b/web/static/js/solar_graph.js index 559d935..c4a0bf6 100644 --- a/web/static/js/solar_graph.js +++ b/web/static/js/solar_graph.js @@ -1,14 +1,12 @@ var hours = 24; -var granularity = '2m'; +var granularity = '5m'; var end = 0; -var graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=24h&granularity=2m&end=0h"; +var retention = 'monthly'; +var graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=24h&granularity=5m&end=0h&retention=monthly"; sol = new Dygraph( -// containing div document.getElementById("solar"), -// CSV or path to a CSV file. graphdata ,{ - //labels: ['time','V_solar','Isolar', P_solar, P_cons], axes : { x : { drawGrid: true, @@ -16,80 +14,82 @@ graphdata }, y : { drawGrid: false, - drawAxis : true + drawAxis: true, + valueRange: [44.5,55] }, y2 : { drawGrid: false, drawAxis: true, - independentTicks: true + independentTicks: true, + customBars: true, + valueRange: [0,1300] } }, - rollPeriod: 5, - visibility: [true, false, true, true], + rollPeriod: 3, interactionModel: {}, connectSeparatedPoints: true, series:{ - 'V_solar': { - axis: 'y', - color: '#ffd020', - fillGraph: true, - fillAlpha: 0.4 - }, - 'I_solar': { - axis: 'y', - color: '#ff1100' - }, 'P_solar': { axis: 'y2', - color: '#1111ff', - fillGraph: true, - fillAlpha: 0.4 + color: '#ff5500' }, - 'P_cons': { - axis: 'y2', - color: '#ff1111', - fillGraph: true, - fillAlpha: 0.4 + 'V_array': { + axis: 'y', + color: '#666' } - }, - ylabel: '[V]/[A]', - y2label: 'Solar / Consumption [W]', + ylabel: 'Battery [V]', + y2label: 'Power [W]', labelsDiv: 'solar_labels', - legend: 'always' + legend: 'always', + customBars: true } ); function refreshGraph(){ - graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h"; + graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h&retention=" + retention; sol.updateOptions({'file': graphdata}); - //power.updateOptions({'file': graphdata}); } function setHours(hours_to_set){ hours = hours_to_set; switch(hours){ case '1': - granularity = '30s'; + granularity = '10s'; + retention = 'monthly'; break; case '6': - granularity = '1m'; + granularity = '10s'; + retention = 'monthly'; break; case '12': - granularity = '1m'; + granularity = '2m'; + retention = 'monthly'; break; case '24': - granularity = '2m'; + granularity = '5m'; + retention = 'monthly'; break; case '168': - granularity = '20m'; + granularity = '15m'; + retention = 'monthly'; break; case '720': - granularity = '1h'; + granularity = '3h'; + retention = 'yearly'; + break; + case '8760': + granularity = '6h'; + retention = 'yearly'; + break; + case '87600': + granularity = '24h'; + retention = 'yearly'; break; default: - granularity = '10m'; + granularity = '5m'; + retention = 'monthly'; } end = 0; //document.getElementById('xxx').innerHTML = graphdata; @@ -118,3 +118,49 @@ function setForth(){ //document.getElementById('xxx').innerHTML = graphdata; refreshGraph(); } + +function getPageContents(callback,url,params) { + if (window.XMLHttpRequest){ + // code for IE7+, Firefox, Chrome, Opera, Safari, SeaMonkey + xmlhttp=new XMLHttpRequest(); + } + else{ + // code for IE6, IE5 + xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); + } + if(params!=null) { + xmlhttp.open("POST", url, true); + xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } else { + xmlhttp.open("GET", url, true); + } + xmlhttp.onreadystatechange = function() { + if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { + callback(xmlhttp.responseText); + } + } + xmlhttp.send(params); +} + +counter = 0; +function refreshValues(){ + fresh_vals_url = '/data/solar_realtime_data?type=json' + getPageContents(function(result){freshVals=JSON.parse(result);}, + fresh_vals_url) + document.getElementById('timestamp').innerHTML = freshVals.time; + document.getElementById('array_voltage').innerHTML = freshVals.V_array; + document.getElementById('array_percent').innerHTML = freshVals.perc_array; + document.getElementById('charge_current').innerHTML = freshVals.ChCurr; + document.getElementById('solar_power').innerHTML = freshVals.Psol; + document.getElementById('pmax_day').innerHTML = freshVals.Pmax_day; + counter = counter + 5000; + if(counter >= 360000){ + refreshGraph(); + document.getElementById('graph_timestamp').innerHTML = freshVals.time; + counter = 0; + } +} + + + +var intervalVal = setInterval(refreshValues, 5000); diff --git a/web/static/js/weather_graph.js b/web/static/js/weather_graph.js index d6c4aaf..d73dff1 100644 --- a/web/static/js/weather_graph.js +++ b/web/static/js/weather_graph.js @@ -1,16 +1,53 @@ -var hours = 24; -var granularity = '1m'; -var end = 0; -var winddata = "https://bastart.spoton.cz/data/wind_monitor?range=24h&granularity=1m&end=0h"; -var temphumidata = "https://bastart.spoton.cz/data/temphumi_monitor?range=24h&granularity=2m&end=0h"; -var pressuredata = "https://bastart.spoton.cz/data/pressure_monitor?range=168h&granularity=2m&end=0h"; +//defaults +var default_granularity = '5m'; // needs to be specified as string, changes +var default_hours = 24; // int is OK, (h) hardcoded +var default_end = 0; // detto +var base_url = "https://bastart.spoton.cz/data/"; +var counter = 0; +var tout = 5000; // fresh date refresh rate, in ms +var g_tout = 30000; // graph update rate, in ms + +//initialize the uninitialized +var hours = new Object(); +var granularity = new Object(); +var end = new Object(); +var graphdata = new Object(); +var data = new Object(); + +// types match function names in python's Dynamic.py class +var types = Array('wind_monitor', + 'temphumi_monitor', + 'pressure_monitor', + 'rrate_monitor'); + +// Now let's set up the defaults and primary data sources, leter changed dynamically +var types_size = types.length; +for(var i=0; iSpeed / Gusts [km/h]', y2label: 'Direction [°]', @@ -55,12 +91,9 @@ winddata ); temphumi_out = new Dygraph( -// containing div document.getElementById("temphumi"), -// CSV or path to a CSV file. -temphumidata -,{ - //labels: [time,T(ins),T(out),Humi(ins),Humi(out)], +data['temphumi_monitor'], +{ axes : { x : { drawGrid: true, @@ -76,7 +109,7 @@ temphumidata independentTicks: true } }, - rollPeriod: 10, + rollPeriod: 5, visibility: [false, true, false, true], interactionModel: {}, connectSeparatedPoints: true, @@ -101,7 +134,6 @@ temphumidata axis: 'y2', color: '#222288' } - }, ylabel: 'Outside [°C]', y2label: 'Humidity [%]', @@ -111,12 +143,9 @@ temphumidata ); temphumi_in = new Dygraph( -// containing div document.getElementById("temphumi_in"), -// CSV or path to a CSV file. -temphumidata -,{ - //labels: [time,T(ins),T(out),Humi(ins),Humi(out)], +data['temphumi_monitor'], +{ axes : { x : { drawGrid: true, @@ -134,7 +163,7 @@ temphumidata valueRange: [0,100] } }, - rollPeriod: 10, + rollPeriod: 5, visibility: [true, false, true, false], interactionModel: {}, connectSeparatedPoints: true, @@ -159,7 +188,6 @@ temphumidata axis: 'y2', color: '#222288' } - }, ylabel: 'Inside [°C]', y2label: 'Humidity [%]', @@ -169,12 +197,9 @@ temphumidata ); pressure = new Dygraph( -// containing div document.getElementById("pressure"), -// CSV or path to a CSV file. -pressuredata -,{ - //labels: [time,Pressure], +data['pressure_monitor'], +{ axes : { x : { drawGrid: true, @@ -191,7 +216,7 @@ pressuredata valueRange: [970,1055] } }, - rollPeriod: 10, + rollPeriod: 20, interactionModel: {}, connectSeparatedPoints: true, visibility: [true, false], @@ -208,7 +233,6 @@ pressuredata fillGraph: true, fillAlpha: 0.5 } - }, ylabel: 'Pressure [hPa]', labelsDiv: 'pressure_labels', @@ -216,81 +240,175 @@ pressuredata } ); +rrate = new Dygraph( +document.getElementById("rrate"), +data['rrate_monitor'], +{ + axes : { + x : { + drawGrid: true, + drawAxis : true + }, + y : { + drawGrid: false, + drawAxis : true, + valueRange: [0,3] + }, + y2 : { + drawGrid: false, + drawAxis: true, + valueRange: [0,3] + } + }, + rollPeriod: 0, + interactionModel: {}, + connectSeparatedPoints: true, + visibility: [true, true], + series:{ + 'Rrate': { + axis: 'y', + color: '#0055ff', + fillGraph: true, + fillAlpha: 0.5 + }, + 'Rrate_max': { + axis: 'y2', + color: '#0000aa', + fillGraph: false + } + }, + ylabel: 'Rain rate [mm/h]', + labelsDiv: 'rrate_labels', + legend: 'always' + } +); +// Functions for buttons to scale / move graphs function refreshGraph(source){ - graphdata = "https://bastart.spoton.cz/data/" + source + "?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h"; + graphdata[source] = "https://bastart.spoton.cz/data/" + source + "?range=" + hours[source] + "h&granularity=" + granularity[source] + "&end=" + end[source] + "h"; if(source == 'wind_monitor'){ - wind.updateOptions({'file': graphdata}); + wind.updateOptions({'file': graphdata[source]}); } if(source == 'temphumi_monitor'){ - temphumi_out.updateOptions({'file': graphdata}); - temphumi_in.updateOptions({'file': graphdata}); + temphumi_out.updateOptions({'file': graphdata[source]}); + temphumi_in.updateOptions({'file': graphdata[source]}); } if(source == 'pressure_monitor'){ - pressure.updateOptions({'file': graphdata}); + pressure.updateOptions({'file': graphdata[source]}); } - //document.getElementById('xxx').innerHTML = source; - //power.updateOptions({'file': graphdata}); + if(source == 'rrate_monitor'){ + rrate.updateOptions({'file': graphdata[source]}); + } + // TODO: adjust to update graph timestamps per click on ranges + // document.getElementById('g_tstamp_' + source).innerHTML = freshVals.time; } +// here, target needs to match the python's dynamic class again function setHours(hours_to_set, target){ - hours = hours_to_set; - switch(hours){ + hours[target] = hours_to_set; + switch(hours[target]){ case '1': - granularity = '1m'; - if(target == 'temphumi_monitor'){ granularity = '2m';} + granularity[target] = '30s'; + if(target == 'temphumi_monitor'){ granularity[target] = '2m';} break; case '6': - granularity = '1m'; - if(target == 'temphumi_monitor'){ granularity = '2m';} + granularity[target] = '1m'; + if(target == 'temphumi_monitor'){ granularity[target] = '2m';} break; case '12': - granularity = '1m'; - if(target == 'temphumi_monitor'){ granularity = '2m';} + granularity[target] = '2m'; + if(target == 'temphumi_monitor'){ granularity[target] = '2m';} break; case '24': - granularity = '1m'; - if(target == 'temphumi_monitor'){ granularity = '2m';} + granularity[target] = '5m'; + if(target == 'temphumi_monitor'){ granularity[target] = '2m';} break; case '168': - granularity = '10m'; + granularity[target] = '10m'; break; case '720': - granularity = '1h'; + granularity[target] = '1h'; break; case '4320': - granularity = '6h'; + granularity[target] = '12h'; break; case '8640': - granularity = '1d'; + granularity[target] = '24h'; break; default: - granularity = '10m'; + granularity[target] = default_granularity; } - end = 0; - //document.getElementById('xxx').innerHTML = target; + end[target] = 0; refreshGraph(target); } +// Functions for buttons to scale / move graphs function setBack(target){ - // range=1h -> range=2h&end=1h - disp_range = hours*1 - end*1; - hours = hours*1 + disp_range; - end = end*1 + disp_range; - //document.getElementById('xxx').innerHTML = graphdata; + disp_range = hours[target]*1 - end[target]*1; + hours[target] = hours[target]*1 + disp_range; + end[target] = end[target]*1 + disp_range; refreshGraph(target); } function setForth(target){ - disp_range = hours*1 - end*1; - hours = hours*1 - disp_range; - if(hours < disp_range){ - hours = disp_range; + disp_range = hours[target]*1 - end[target]*1; + hours[target] = hours[target]*1 - disp_range; + if(hours[target] < disp_range){ + hours[target] = disp_range; } - end = end*1 - disp_range; - if(end < 0){ - end = 0; + end[target] = end[target]*1 - disp_range; + if(end[target] < 0){ + end[target] = 0; } - //document.getElementById('xxx').innerHTML = graphdata; refreshGraph(target); } + +function getPageContents(callback,url,params) { + if (window.XMLHttpRequest){ + // code for IE7+, Firefox, Chrome, Opera, Safari, SeaMonkey + xmlhttp=new XMLHttpRequest(); + } + else{ + // code for IE6, IE5 + xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); + } + if(params!=null) { + xmlhttp.open("POST", url, true); + xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } else { + xmlhttp.open("GET", url, true); + } + xmlhttp.onreadystatechange = function() { + if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { + callback(xmlhttp.responseText); + } + } + xmlhttp.send(params); +} + +function refreshValues(){ + fresh_vals_url = '/data/weather_realtime_data?type=json' + getPageContents(function(result){freshVals=JSON.parse(result);}, + fresh_vals_url) + document.getElementById('timestamp').innerHTML = freshVals.time; + document.getElementById('w_speed_km').innerHTML = freshVals.wind.speed; + document.getElementById('w_speed_ms').innerHTML = freshVals.wind.speed_ms; + document.getElementById('w_dir').innerHTML = freshVals.wind.dir; + document.getElementById('w_gust_km').innerHTML = freshVals.wind.gust; + document.getElementById('w_gust_ms').innerHTML = freshVals.wind.gust_ms; + document.getElementById('w_dir_name').innerHTML = freshVals.wind.dir_name; + document.getElementById('out_temp').innerHTML = freshVals.temperature.out; + document.getElementById('in_temp').innerHTML = freshVals.temperature.in; + document.getElementById('current_pressure').innerHTML = freshVals.pressure; + document.getElementById('current_rrate').innerHTML = freshVals.rainrate; + counter = counter + tout; + if(counter >= g_tout){ + for(var i=0; i - Created in 101 minutes, in VI, while sitting in a train Wien -> Bratislava. - Uploeaded over EDGE with a lot of signal-less territory on roaming. Cheers. + Measurements utilise Arduino, ESP8266, lot of python, another lot + of Micropython, tons of "heart", no cocaine, but shitloads of Tea. Cheers. Done by Ventil. diff --git a/web/templates/header.html b/web/templates/header.html index 3d63d5a..ed6dceb 100644 --- a/web/templates/header.html +++ b/web/templates/header.html @@ -10,14 +10,37 @@ - + - + - + - - + + + + diff --git a/web/templates/status_admin.html b/web/templates/status_admin.html old mode 100644 new mode 100755 index 8810aef..2317890 --- a/web/templates/status_admin.html +++ b/web/templates/status_admin.html @@ -4,19 +4,35 @@

Status Dashboard

-
{timestamp} UTC
+
{timestamp} UTC
+

Up since: {_uptime}

ISS solar irradiation -

{sol_value}

+

{_sol_value}

[V]
ISS Capacitor state -

{cap_value}

+

{_cap_value}

[V]
+
+ CPU % +

{_cpu}

+
[%]
+
+
+ Mem / Disk % +

{_mem} / {_disk}

+
[%]
+
+
+ RasPI network +

{_net_in} / {_net_out}

+
IN [kB/s] / OUT [kB/s]
+
@@ -31,6 +47,7 @@ +
{timestamp} UTC
@@ -47,8 +64,39 @@ +
{timestamp} UTC
- +
+
+
+
+
+ + + + + + + + + + +
{timestamp} UTC
+
+
+
+
+
+
+ + + + + + +
{timestamp} UTC
+
+
diff --git a/web/templates/top_menu.html b/web/templates/top_menu.html index 1b7b363..5b25a8a 100644 --- a/web/templates/top_menu.html +++ b/web/templates/top_menu.html @@ -2,7 +2,7 @@ - Monitoring + Monitoring diff --git a/web/templates/voltage_admin.html b/web/templates/voltage_admin.html index 48408f2..b3660f8 100644 --- a/web/templates/voltage_admin.html +++ b/web/templates/voltage_admin.html @@ -4,31 +4,26 @@

Energy Dashboard

- +
{timestamp} UTC
Battery voltage -

{array_voltage} [V]

-
{array_percent} [%]
+

{array_voltage} [V]

+
{array_percent} [%]
- Battery current -

{charge_current}

+ Battery current +

{charge_current}

Charging [A]
- Solar array power -

{solar_power}

+ Solar array power +

{solar_power}

Solar PV [W]
- Solar array voltage -

{voltage_solar}

-
Solar [V]
-
-
- Max power / day -

{pmax_day}

+ Max power / day +

{pmax_day}

Pmax / day [W]
@@ -45,19 +40,15 @@ + +
{timestamp} UTC
-

Historical values by day

- {history_table} -
-
{stats_table} - {fresh_table} -
diff --git a/web/templates/weather_admin.html b/web/templates/weather_admin.html index 9bda8d5..b68f916 100644 --- a/web/templates/weather_admin.html +++ b/web/templates/weather_admin.html @@ -4,40 +4,50 @@

Weather Dashboard

-
{timestamp} UTC
+
{_timestamp} UTC
- Wind Speed -

{w_speed_km} [km/h]

-
{w_speed_ms} [m/s]
+ Wind Speed +

{_w_speed_km} [km/h]

+
+ {_w_speed_ms} [m/s] +
- Wind Gusts -

{w_gust_km} [km/h]

-
{w_gust_ms} [m/s]
+ Wind Gusts +

{_w_gust_km} [km/h]

+
+ {_w_gust_ms} [m/s] +
- Wind Direction -

{w_dir_name}

-
{w_dir_deg} °
+ Wind Direction +

{_w_dir_name}

+
{_w_dir_deg} °
+
-
- Outside Temperature -

{out_temp} °C

+
+ Outside Temperature +

{_out_temp} °C

[Outside]
-
- Inside Temperature -

{in_temp} °C

+
+ Inside Temperature +

{_in_temp} °C

[Inside]
-
- Atmospheric pressure -

{pressure} [hPa]

-
{raw_pressure} [hPa] raw
+
+ Atmospheric pressure +

{_pressure} [hPa]

+
{_raw_pressure} [hPa] raw
+
+
+ Rain rate +

{_rrate}

+
[mm/h]
@@ -56,6 +66,7 @@ +
{_timestamp} UTC
@@ -74,6 +85,7 @@ +
{_timestamp} UTC
@@ -88,6 +100,22 @@ +
{_timestamp} UTC
+ +
+
+
+
+
+ + + + + + + + +
{_timestamp} UTC
diff --git a/web/templates/weather_admin.html.orig b/web/templates/weather_admin.html.orig deleted file mode 100644 index 07c77f1..0000000 --- a/web/templates/weather_admin.html.orig +++ /dev/null @@ -1,280 +0,0 @@ -
-
- - -
-
-

Weather Dashboard

-
{timestamp}
- -
-
- Wind Speed -

{w_speed_km} [km/h]

-
{w_speed_ms} [m/s]
-
-
- Wind Gusts -

{w_gust_km} [km/h]

-
{w_gust_ms} [m/s]
-
-
- Wind Direction -

{w_dir_name}

-
{w_dir_deg} °
-
- -
-
- Outside Temperature -

{out_temp} °C

-
[Outside]
-
-
- Inside Temperature -

{in_temp} °C

-
[Inside]
-
-
- Atmospheric pressure -

{pressure} [hPa] Adjusted

-
{raw_pressure} [hPa] Actual
-
- -
-
-

Wind speed and direction

-
-
-
- - - - - - - - -
-
-
-
-
-

Temperature and Humidity

-
-
-
- - - - - - - -<<<<<<< HEAD - -======= - ->>>>>>> ca62f04757f1010262e5559bbd8753bfa49bf34e -
-
-
-
-
-
-
- -
- -
-
- - -
-
- - - - - - - - -