diff --git a/client/jsFiles/SEAWebClientCommunication.js b/client/jsFiles/SEAWebClientCommunication.js index 3a8f81d..ff7d8cb 100644 --- a/client/jsFiles/SEAWebClientCommunication.js +++ b/client/jsFiles/SEAWebClientCommunication.js @@ -86,7 +86,7 @@ function handleUpdateMessage(src, message) { sizeChange(); } else { clientTitle = message.instrument + " " + message.device; - console.log('loadBlocks') + console.log('loadBlocks', message); loadFirstBlocks(); } document.title = "SEA "+clientTitle; @@ -155,7 +155,6 @@ function handleUpdateMessage(src, message) { case "graph-update": //if (getUpdatesGraphics) { //timeServer = message.time; - console.log("graph-update"); updateCharts2(message.graph); //} break; @@ -207,11 +206,12 @@ function updateValues(message, src) { } else if (type == "input") { var row = matches[j].parentNode.parentNode.parentNode; row.style.backgroundColor = "white"; - var oldValue = matches[j].getAttribute("oldValue") - || matches[j].value; - if (value != matches[j].value && value != oldValue) { + var mval = matches[j].value; + var oldValue = matches[j].getAttribute("oldValue"); + if (oldValue === null) oldValue = mval; + if (value != mval && parseFloat(value) != parseFloat(mval) && value != oldValue) { if (matches[j] == document.activeElement - || oldValue != matches[j].value) { + || oldValue != mval) { row.style.backgroundColor = "orange"; } else { matches[j].value = value; diff --git a/client/jsFiles/SEAWebClientGraph.js b/client/jsFiles/SEAWebClientGraph.js index 1a9c038..4a54cf4 100644 --- a/client/jsFiles/SEAWebClientGraph.js +++ b/client/jsFiles/SEAWebClientGraph.js @@ -106,51 +106,20 @@ function doubleTap(callback){ return {stop: function(){ window.removeEventListener('touchend', handler) }} } -function maxAr(array){ +function maxAr(array, tmin, tmax){ return Math.max.apply(Math, array.map(function(o) { - if (o.y == null) return -1e99; + if (o.y == null || o.x < tmin || o.x > tmax) return -1e99; return o.y; })); } -function minAr(array){ +function minAr(array, tmin, tmax){ return Math.min.apply(Math, array.map(function(o) { - if (o.y == null) return 1e99; + if (o.y == null || o.x < tmin || o.x > tmax) return 1e99; return o.y; })); } -function autoScale(chart) { - axis = chart.options.scales.yAxes[0] - datasets = chart.data.datasets; - let max = -1e99; - let min = 1e99; - for (testwidth = 1; testwidth >= 0; testwidth--) { - for(let i = 0; i < datasets.length; i++){ - ds = datasets[i]; - if (ds.borderWidth == testwidth) continue; - let lmax = maxAr(ds.data); - let lmin = minAr(ds.data); - if(lmax > max) - max = lmax; - if(lmin < min) - min = lmin; - } - if (min < max) { - break; - } else if (min == max) { - max = min + 0.5; - min = max - 1.0; - break; - } - } - //console.log('autoScale', min, max); - axis.ticks.min = min - (max - min) * 0.05; - axis.ticks.max = max + (max - min) * 0.01; - axis.min = axis.ticks.min; - axis.max = axis.ticks.max; -} - function strFormat(str, significant_digits) { if (str == null) return ''; evalue = str.toExponential(significant_digits-1).replace(/0*e/, 'e').replace(/\.e/, 'e').replace("e+", "e"); @@ -163,7 +132,7 @@ function strFormat(str, significant_digits) { let graphs = (function (){ let dataset_to_graph_map = {}, - blocks, doUpdates=true, top_vars=[], bottom_vars=[], zoomed =false; + blocks, liveMode=true, top_vars=[], bottom_vars=[], zoomed =false; let type = 'linear'; @@ -175,6 +144,8 @@ let graphs = (function (){ let container = document.createElement('div'); container.classList.add("graphs-container"); + let currentMinTime = 0; + let currentMaxTime = 0; for (let i = 0; i < ngraphs; i++) { let gr = document.createElement('div'); @@ -242,7 +213,7 @@ let graphs = (function (){ } //let varlist = top_vars.concat(bottom_vars); varlist = vars_array[gindex]; - let el =graph_array[gindex]; + let el = graph_array[gindex]; AJAX("http://" + hostPort + "/graph?time=" + minTime/1000 + "," + maxTime/1000 + "&variables=" + varlist + "&id=" + clientID).getJSON().then(function(data){ //console.log('Graph', block, data) @@ -255,13 +226,10 @@ let graphs = (function (){ } let pdata = []; for(let e of data.graph[key]){ - //if(e[1] == null || e[1] == null){ - // continue; - //} pdata.push({x: e[0]*1000, y: e[1]}); } if(pdata.length > 0){ - addDataset(gindex, key, [dict[key].label, dict[key].color, pdata]) + addDataset(gindex, key, [dict[key].label, dict[key].color, pdata, dict[key].continuous]) /*console.log(timeRange); if(data[data.length-1].x-data[0].x > d && data[data.length-1].x-data[0].x < (30*60+10)*1000){ // Adjust to requested time d = data[data.length-1].x-data[0].x @@ -271,20 +239,91 @@ let graphs = (function (){ } } chart.setMinMax(minTime,maxTime); - // chart.autoScaleChart(); + chart.autoScaleIf(); chart.update(); - AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON(); // why this? + result = AJAX( "http://" + hostPort + + "/updategraph?variables=" + variables() + + "&id=" + clientID).getJSON().then(function(data) { + liveMode = data.live; + console.log('LIVE create', liveMode) + }) + //console.log('UPDATE LIVE', result); }) } // add dataset to graph with graph_id function addDataset(gindex, key, dataset){ let g = chart_array[gindex]; - dataset_to_graph_map[key] = [gindex, g.addDataset(dataset)]; + dataset_to_graph_map[key] = [gindex, g.addDataset(key, dataset)]; + } + + function autoScale(chart) { + axis = chart.options.scales.yAxes[0]; + tax = chart.options.scales.xAxes[0].ticks; + datasets = chart.data.datasets; + let max = -1e99; + let min = 1e99; + // if there are datasets with values and think lines, + // consider them only. if not, consider all (second pass in the following loop) + let extraMin = min; + let extraMax = max; + for (testwidth = 1; testwidth >= 0; testwidth--) { + for (let i = 0; i < datasets.length; i++){ + ds = datasets[i]; + if (ds.borderWidth <= testwidth) continue; + let lmax = maxAr(ds.data, tax.min, tax.max); + let lmin = minAr(ds.data, tax.min, tax.max); + if(lmax > max) + max = lmax; + if(lmin < min) + min = lmin; + if (ds.data.length && liveMode) { + lasty = ds.data.slice(-1)[0].y; + console.log('LASTY', lasty); + extraMin = Math.min(extraMin, lasty); + extraMax = Math.max(extraMax, lasty); + } + } + if (min > max) continue; // do a second pass over all curves + break; + } + if (min > max) return; + if (min == max) { + if (min == 0) { + ystep = 1; + } else { + ystep = Math.abs(min * 0.01); + } + min -= ystep; + max += ystep; + } else { + ystep = (max - min) * 0.1; + if (liveMode) { + extraMin = Math.min(min, extraMin - ystep); + extraMax = Math.max(max, extraMax + ystep); + } else { + extraMin = min - ystep * 0.5; + extraMax = max + ystep * 0.5; + } + if (min >= axis.ticks.min && axis.ticks.min >= extraMin && + max <= axis.ticks.max && axis.ticks.max <= extraMax) { + console.log('NOCHANGE') + return; // do not yet change + } + console.log(min, axis.ticks.min, extraMin) + console.log(max, axis.ticks.max, extraMax) + min = extraMin; + max = extraMax; + } + //console.log('autoScale', min, max, tax.min, tax.max); + axis.min = axis.ticks.min = min; + axis.max = axis.ticks.max = max; } function setMinMax(min, max){ + currentMaxTime = max; + currentMinTime = min; for (let ch of chart_array) { if (ch) ch.setMinMax(min, max); } @@ -326,6 +365,16 @@ let graphs = (function (){ chart_array[i[0]].reloadData(i[1], data); } + function variables() { + let vardict = {}; + for (let vars of vars_array) { + for (let v of vars) { + vardict[v] = 1; + } + } + return Object.keys(vardict); + } + function reloadData(min, max){ min = min/1000; @@ -334,14 +383,8 @@ let graphs = (function (){ }else{ max = max/1000; } - let vardict = {}; - for (let vars of vars_array) { - for (let v of vars) { - vardict[v] = 1; - } - } - AJAX("http://" + hostPort + "/graph?time=" + min + ","+max+"&variables=" + Object.keys(vardict) + "&id=" + clientID).getJSON().then(function(data){ + AJAX("http://" + hostPort + "/graph?time=" + min + ","+max+"&variables=" + variables() + "&id=" + clientID).getJSON().then(function(data){ for(let key in data.graph){ let pdata = []; for(let e of data.graph[key]){ @@ -354,10 +397,13 @@ let graphs = (function (){ reloadDataFlag(key, pdata); } } - for (let ch of chart_array) { - if (ch) ch.autoScaleChart(); // should depend on a flag - } - AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON(); // why this ? + // AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON(); // activate updates + result = AJAX("http://" + hostPort + + "/updategraph?variables=" + variables() + + "&id=" + clientID).getJSON().then(function(data) { + liveMode = data.live; + console.log('LIVE reload', liveMode) + }) update(); }); } @@ -365,19 +411,21 @@ let graphs = (function (){ function checkReload(chart){ let xmin = chart.options.scales.xAxes[0].ticks.min, xmax = chart.options.scales.xAxes[0].ticks.max; - if(xmax < now()-10000){ // was 100000 = 100sec - doUpdates = false; + if (xmax < now()-100000) { // was 100000 = 100sec + if (liveMode) console.log('UPDATES OFF?') + //doUpdates = false; }else{ - doUpdates = true; + if (!liveMode) console.log('UPDATES ON?') + //doUpdates = true; } - if(xmin < minTime || xmax > maxTime || xmax - xmin < 0.5 * (maxTime - minTime)){ + if (xmin < minTime || xmax > maxTime || xmax - xmin < 0.5 * (maxTime - minTime)) { //TODO: the criterium for getting finer resolution data should depend, if better res. is available // this information has to come from the server reloadData(xmin, xmax); minTime = xmin; maxTime = xmax; } else { - autoScale(chart); // TODO: must depend on autoScale flag + if (chart.autoScaleFlag) autoScale(chart); chart.update(); } } @@ -387,7 +435,9 @@ let graphs = (function (){ xmax = chart.options.scales.xAxes[0].ticks.max; setMinMax(xmin,xmax); + //console.log('zoompan', autoScaleFlag); for (let ch of chart_array) { + ch.autoScaleIf(); if (ch) ch.redraw(redrawX); } update(); @@ -399,15 +449,27 @@ let graphs = (function (){ } } - - let g_varlist = []; - function updateAllDisabled(){ - // not used anymore? - AJAX("http://" + hostPort + "/graph?time="+timeRange+"&variables=" + g_varlist + "&id=" + clientID).getJSON().then(function(a){ - AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON() - }); + function updateAuto(){ + if (liveMode) { + max = now(); + if (currentMaxTime && max > currentMaxTime) { + max = currentMaxTime + Math.min(60000, 0.1 * (currentMaxTime - currentMinTime)); + setMinMax(currentMinTime, max); + reloadData(currentMinTime, max); + return; + } + } + for (let ch of chart_array) { + if (ch) { + ch.autoScaleIf(); + ch.update(); + } + } } + /* + let g_varlist = []; + function getVarlist(blocks){ var varlist = []; for (var i = 0; i < blocks.length; i++) { @@ -417,6 +479,7 @@ let graphs = (function (){ } return varlist; } + */ let startTime, recvTime, minTime, maxTime; @@ -425,13 +488,14 @@ let graphs = (function (){ minTime = timeRange[0]*1000; AJAX("http://" + hostPort + "/gettime?time=-1800,0&id="+ clientID).getJSON().then(function(data){ startTime = data.time[1]*1000; - maxTime = startTime; + maxTime = startTime + 60000; + console.log('MAXTIME', maxTime - Date.now()); minTime = data.time[0]*1000; recvTime = performance.now(); }); - g_varlist = getVarlist(nblocks) + // g_varlist = getVarlist(nblocks) let f = 0; insertSlide(f, "graphics", "graphics", container); blocks = nblocks; @@ -514,13 +578,15 @@ let graphs = (function (){ zoompan: zoompan, receivedVars: receivedVars, createSelection: createSelection, - doUpdates: function(){return doUpdates}, + doUpdates: function(){return liveMode}, update: update, + updateAuto: updateAuto, now: now, zoomed: zoomed, checkReload: checkReload, getBlocks: getBlocks, createGraph: createGraph, + autoScale: autoScale, } })(); @@ -567,7 +633,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ y1 = ticks[0]; y0 = ticks.slice(-1)[0]; span = y1 - y0; - step = Math.abs(span * 0.1).toExponential(0); + step = Math.abs(span * 0.3).toExponential(0); if (step[0] > '5') { step = '5' + step.substr(1); } else if (step[0] > '2') { @@ -651,6 +717,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ } } }); + let autoScaleFlag = true; //console.log('create legend') let legend = document.createElement('div'); @@ -724,9 +791,8 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ graphs.zoompan(chart); });*/ - addControl("Autoscale Y", function(){ - autoScale(chart); - update(); + let autoScaleRow = addControl("Autoscale Y on off", function(){ + toggleAutoScale(); }); addControl("Go to now", function(){ @@ -847,60 +913,91 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ chart.update(); } - function addDataset(data){ + function addDataset(key, data){ let dataset_index = chart.data.datasets.length; - chart.data.datasets.push({data: data[2], label: data[0], origLabel: data[0], - spanGaps: false, lineJoin: 'round', borderWidth: 2, + chart.data.datasets.push({data: data[2], label: data[0], key: key, + spanGaps: false, lineJoin: 'round', borderWidth: 2, steppedLine: data[3] == 0, borderColor: data[1],fill: false, pointRadius: 0, tension:0, showLine: true}); let dataset = chart.data.datasets[dataset_index]; let legendel = document.createElement('div'); let legendelvalue = document.createElement('div'); legendelvalue.classList.add('value'); - legendels[data[0]] = legendelvalue; + legendels[key] = legendelvalue; legendel.classList.add('legendel') let color = document.createElement('div'); color.classList.add('color') color.style.backgroundColor = dataset.borderColor; legendel.appendChild(color); - legendel.innerHTML += dataset.label; - legendel.addEventListener('click', function(){ + let dlabel = document.createElement('div'); + dlabel.innerHTML = dataset.label; + dlabel.addEventListener('click', function(evt){ + /* + console.log('LABEL', evt.target) if(legendmoving) return - let meta = dataset._meta[Object.keys(dataset._meta)[0]] - /* - if(meta.hidden == null){ - meta.hidden = true; - addClass(legendel, 'hidden'); - }else{ - meta.hidden = null; - delClass(legendel, 'hidden'); - } + legendel.firstChild.style.height = '2px'; + dataset.borderWidth = 2; + labelClicked = true; + //console.log('LABEL', evt.target) */ - if (dataset.borderWidth == 1) { - dataset.borderWidth = 2; - } else { - dataset.borderWidth = 1; - } - autoScale(chart); - chart.update(); - }) + }); + legendel.appendChild(dlabel); legendel.appendChild(legendelvalue); + legendel.addEventListener('click', function(evt){ + if (legendmoving) return; + for (let k in legendels) { + // set all labels to normal font + legendels[k].parentNode.children[1].style.fontWeight = 400; + } + if (evt.target == dlabel) { + // disable all + for (let k in legendels) { + legendels[k].parentNode.firstChild.style.height = '1px'; + } + for (ds of chart.data.datasets) { + ds.borderWidth = 1; + } + color.style.height = '2px'; + dataset.borderWidth = 2; + dlabel.style.fontWeight = 700; // bold + } else { + if (dataset.borderWidth == 1) { + legendel.firstChild.style.height = '2px'; + dataset.borderWidth = 2; + } else { + legendel.firstChild.style.height = '1px'; + dataset.borderWidth = 1; + } + } + graphs.autoScale(chart); + chart.update(); + }); legend.appendChild(legendel); return dataset_index; } - function autoScaleChart() { - autoScale(chart); + function autoScaleIf() { + if (autoScaleFlag) graphs.autoScale(chart); } function pushData(dataset_index, data_point){ - chart.data.datasets[dataset_index].data.push(data_point); - if(!graphs.zoomed){ - chart.options.scales.xAxes[0].ticks.max = data_point.x; - }else{ - update_max = data_point.x; + data = chart.data.datasets[dataset_index].data; + //if (chart.data.datasets[dataset_index].key == 'tt:target') + // console.log('BEFORE', data.slice(-3)) + if (data.slice(-1)[0] && data.slice(-1)[0].x >= data_point.x) { + removed = data.pop(); } + data.push(data_point); + //if (chart.data.datasets[dataset_index].key == 'tt:target') + // console.log('PUSHED', data.slice(-3)) + /* + if (graphs.zoomed) { + update_max = data_point.x; + } else { + chart.options.scales.xAxes[0].ticks.max = data_point.x; + } + */ } function reloadData(index, data){ @@ -959,6 +1056,17 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ ax.ticks.min = min; } + function toggleAutoScale () { + autoScaleFlag = !autoScaleFlag; + if (autoScaleFlag) { + graphs.autoScale(chart); + update(); + autoScaleRow.innerHTML = "Autoscale on off"; + } else { + autoScaleRow.innerHTML = "Autoscale on off"; + } + } + function toggleAxesType(){ setAxesType((chart.options.scales.yAxes[0].type=== 'linear') ? 'logarithmic' : 'linear'); } @@ -972,6 +1080,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ } chart.options.scales.yAxes[0].type = type; chart.options.animation.duration = 800; + if (autoScaleFlag) graphs.autoScale(chart); update(); setTimeout(function(){chart.options.animation.duration = 0;},850) } @@ -996,7 +1105,7 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ let d2 = Math.abs(dp._model.x - x) if(d == 0 || d2 < d){ d = d2; - legendels[chart.data.datasets[i].label].innerHTML = + legendels[chart.data.datasets[i].key].innerHTML = strFormat(chart.data.datasets[i].data[dp._index].y, 6); test[i]=dp } @@ -1045,7 +1154,8 @@ function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){ redraw: redraw, update: update, reloadData: reloadData, - autoScaleChart: autoScaleChart, + autoScaleIf: autoScaleIf, + chart: chart, } } @@ -1096,13 +1206,17 @@ function updateCharts2(graph){ return; } for(let key in graph){ - for (pt of graph[key]) { - if (graph[key][1] != null) { - // there is at least ONE valid datapoint - graphs.newDataHandler(key, {x: graph[key][0][0]*1000, y: graph[key][0][1]}); - break; + if (graph[key][0] != null) { + // there is at least ONE valid datapoint + for (pt of graph[key]) { + graphs.newDataHandler(key, {x: pt[0]*1000, y: pt[1]}); } } } - graphs.update(); + graphs.updateAuto(); + // graphs.update(); } + +function createCharts2(arg) { + console.log('C2', arg) +} \ No newline at end of file diff --git a/client/jsFiles/SEAWebClientGroup.js b/client/jsFiles/SEAWebClientGroup.js index 98e85de..d0ddf9d 100644 --- a/client/jsFiles/SEAWebClientGroup.js +++ b/client/jsFiles/SEAWebClientGroup.js @@ -250,6 +250,10 @@ function createInput(s, name, title, info) { input.setAttribute("name", name); input.setAttribute("__ctype__", "input"); input.style.width = "100px"; + input.addEventListener("focus", function(evt) { + let elm = evt.target; + setTimeout(function(){elm.setSelectionRange(0, elm.value.length);},0); + }); input.onkeydown = function (e) { if (e.which === 27 || e.key == "Escape") { @@ -281,7 +285,8 @@ function createInput(s, name, title, info) { input.setAttribute("actualValue", oldValue); } var actualValue = input.getAttribute("actualValue"); - if (value === actualValue || value === oldValue) { + if (value == actualValue || value == oldValue || + parseFloat(value) == parseFloat(actualValue) || parseFloat(value) == parseFloat(oldValue)) { input.value = actualValue; // nothing to do. row.style.backgroundColor = "white"; diff --git a/doc/protocol.txt b/doc/protocol.txt index 225370e..cd740c5 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -166,18 +166,25 @@ All other connections are to be closed (getJSON): ********************************************************************************************************************************* -/updategraph?id= +/updategraph?id=[&variables=] + + if is given, the given variables will be updated with the graph-update-message response: = = { - "type": "accept-graph" + "type": "accept-graph", + "live": true/false } + the "live" flag is true when the last /graph query was including the actual time + ********************************************************************************************************************************* /graph?time=,&variables=&id= + the given variables are added to the list of variables being updated with the graph-update-message + response: = = { diff --git a/histgraph.py b/histgraph.py index 775ce36..2facfde 100644 --- a/histgraph.py +++ b/histgraph.py @@ -18,51 +18,67 @@ def get_abs_time(*times): class ColorMap(object): - ''' + """" ColorMap is using official CSS color names, with the exception of Green, as this is defined differently with X11 colors than in SEA, and used heavily in config files. Here Green is an alias to Lime (#00FF00) and MidGreen is #008000, which is called Green in CSS. The function to_code is case insensitive and accepts also names with underscores. The order is choosen by M. Zolliker for the SEA client, originally only the first 16 were used. - ''' - hex_name = (("#FFFFFF","White"), ("#FF0000","Red"), ("#00FF00","Lime"), ("#0000FF","Blue"), ("#FF00FF","Magenta"), - ("#FFFF00","Yellow"), ("#00FFFF","Cyan"), ("#000000","Black"), ("#FFA500","Orange"), ("#006400","DarkGreen"), - ("#9400D3","DarkViolet"), ("#A52A2A","Brown"), ("#87CEEB","SkyBlue"), ("#808080","Gray"), ("#FF69B4","HotPink"), - ("#FFFFE0","LightYellow"), ("#00FF7F","SpringGreen"), ("#000080","Navy"), ("#1E90FF","DodgerBlue"), - ("#9ACD32","YellowGreen"), ("#008B8B","DarkCyan"), ("#808000","Olive"), ("#DEB887","BurlyWood"), - ("#7B68EE","MediumSlateBlue"), ("#483D8B","DarkSlateBlue"), ("#98FB98","PaleGreen"), ("#FF1493","DeepPink"), - ("#FF6347","Tomato"), ("#32CD32","LimeGreen"), ("#DDA0DD","Plum"), ("#7FFF00","Chartreuse"), ("#800080","Purple"), - ("#00CED1","DarkTurquoise"), ("#8FBC8F","DarkSeaGreen"), ("#4682B4","SteelBlue"), ("#800000","Maroon"), - ("#3CB371","MediumSeaGreen"), ("#FF4500","OrangeRed"), ("#BA55D3","MediumOrchid"), ("#2F4F4F","DarkSlateGray"), - ("#CD853F","Peru"), ("#228B22","ForestGreen"), ("#48D1CC","MediumTurquoise"), ("#DC143C","Crimson"), - ("#D3D3D3","LightGray"), ("#ADFF2F","GreenYellow"), ("#7FFFD4","Aquamarine"), ("#BC8F8F","RosyBrown"), - ("#20B2AA","LightSeaGreen"), ("#C71585","MediumVioletRed"), ("#F0E68C","Khaki"), ("#6495ED","CornflowerBlue"), - ("#556B2F","DarkOliveGreen"), ("#CD5C5C","IndianRed "), ("#2E8B57","SeaGreen"), ("#F08080","LightCoral"), - ("#8A2BE2","BlueViolet"), ("#AFEEEE","PaleTurquoise"), ("#4169E1","RoyalBlue"), ("#0000CD","MediumBlue"), - ("#B8860B","DarkGoldenRod"), ("#00BFFF","DeepSkyBlue"), ("#FFC0CB","Pink"), ("#4B0082","Indigo "), ("#A0522D","Sienna"), - ("#FFD700","Gold"), ("#F4A460","SandyBrown"), ("#DAA520","GoldenRod"), ("#DA70D6","Orchid"), ("#E6E6FA","Lavender"), - ("#5F9EA0","CadetBlue"), ("#D2691E","Chocolate"), ("#66CDAA","MediumAquaMarine"), ("#6B8E23","OliveDrab"), - ("#A9A9A9","DarkGray"), ("#BDB76B","DarkKhaki"), ("#696969","DimGray"), ("#B0C4DE","LightSteelBlue"), - ("#191970","MidnightBlue"), ("#FFE4C4","Bisque"), ("#6A5ACD","SlateBlue"), ("#EE82EE","Violet"), - ("#8B4513","SaddleBrown"), ("#FF7F50","Coral"), ("#008000","MidGreen"), ("#DB7093","PaleVioletRed"), ("#C0C0C0","Silver"), - ("#E0FFFF","LightCyan"), ("#9370DB","MediumPurple"), ("#FF8C00","DarkOrange"), ("#00FA9A","MediumSpringGreen"), - ("#E9967A","DarkSalmon"), ("#778899","LightSlateGray"), ("#9932CC","DarkOrchid"), ("#EEE8AA","PaleGoldenRod"), - ("#F8F8FF","GhostWhite"), ("#FFA07A","LightSalmon"), ("#ADD8E6","LightBlue"), ("#D8BFD8","Thistle"), - ("#FFE4E1","MistyRose"), ("#FFDEAD","NavajoWhite"), ("#40E0D0","Turquoise"), ("#90EE90","LightGreen"), - ("#B22222","FireBrick"), ("#008080","Teal"), ("#F0FFF0","HoneyDew"), ("#FFFACD","LemonChiffon"), ("#FFF5EE","SeaShell"), - ("#F5F5DC","Beige"), ("#DCDCDC","Gainsboro"), ("#FA8072","Salmon"), ("#8B008B","DarkMagenta"), ("#FFB6C1","LightPink"), - ("#708090","SlateGray"), ("#87CEFA","LightSkyBlue"), ("#FFEFD5","PapayaWhip"), ("#D2B48C","Tan"), ("#FFFFF0","Ivory"), - ("#F0FFFF","Azure"), ("#F5DEB3","Wheat"), ("#00008B","DarkBlue"), ("#FFDAB9","PeachPuff"), ("#8B0000","DarkRed"), - ("#FAF0E6","Linen"), ("#B0E0E6","PowderBlue"), ("#FFE4B5","Moccasin"), ("#F5F5F5","WhiteSmoke"), ("#FFF8DC","Cornsilk"), - ("#FFFAFA","Snow"), ("#FFF0F5","LavenderBlush"), ("#FFEBCD","BlanchedAlmond"), ("#F0F8FF","AliceBlue"), - ("#FAEBD7","AntiqueWhite"), ("#FDF5E6","OldLace"), ("#FAFAD2","LightGoldenRodYellow"), ("#F5FFFA","MintCream"), - ("#FFFAF0","FloralWhite"), ("#7CFC00","LawnGreen"), ("#663399","RebeccaPurple")) + """ + hex_name = ( + ("#FFFFFF", "White"), ("#FF0000", "Red"), ("#00FF00", "Lime"), ("#0000FF", "Blue"), ("#FF00FF", "Magenta"), + ("#FFFF00", "Yellow"), ("#00FFFF", "Cyan"), ("#000000", "Black"), ("#FFA500", "Orange"), + ("#006400", "DarkGreen"), ("#9400D3", "DarkViolet"), ("#A52A2A", "Brown"), ("#87CEEB", "SkyBlue"), + ("#808080", "Gray"), ("#FF69B4", "HotPink"), ("#FFFFE0", "LightYellow"), ("#00FF7F", "SpringGreen"), + ("#000080", "Navy"), ("#1E90FF", "DodgerBlue"), ("#9ACD32", "YellowGreen"), ("#008B8B", "DarkCyan"), + ("#808000", "Olive"), ("#DEB887", "BurlyWood"), + ("#7B68EE", "MediumSlateBlue"), ("#483D8B", "DarkSlateBlue"), ("#98FB98", "PaleGreen"), ("#FF1493", "DeepPink"), + ("#FF6347", "Tomato"), ("#32CD32", "LimeGreen"), ("#DDA0DD", "Plum"), ("#7FFF00", "Chartreuse"), + ("#800080", "Purple"), ("#00CED1", "DarkTurquoise"), ("#8FBC8F", "DarkSeaGreen"), ("#4682B4", "SteelBlue"), + ("#800000", "Maroon"), + ("#3CB371", "MediumSeaGreen"), ("#FF4500", "OrangeRed"), ("#BA55D3", "MediumOrchid"), + ("#2F4F4F", "DarkSlateGray"), ("#CD853F", "Peru"), ("#228B22", "ForestGreen"), ("#48D1CC", "MediumTurquoise"), + ("#DC143C", "Crimson"), + ("#D3D3D3", "LightGray"), ("#ADFF2F", "GreenYellow"), ("#7FFFD4", "Aquamarine"), ("#BC8F8F", "RosyBrown"), + ("#20B2AA", "LightSeaGreen"), ("#C71585", "MediumVioletRed"), ("#F0E68C", "Khaki"), + ("#6495ED", "CornflowerBlue"), + ("#556B2F", "DarkOliveGreen"), ("#CD5C5C", "IndianRed "), ("#2E8B57", "SeaGreen"), ("#F08080", "LightCoral"), + ("#8A2BE2", "BlueViolet"), ("#AFEEEE", "PaleTurquoise"), ("#4169E1", "RoyalBlue"), ("#0000CD", "MediumBlue"), + ("#B8860B", "DarkGoldenRod"), ("#00BFFF", "DeepSkyBlue"), ("#FFC0CB", "Pink"), ("#4B0082", "Indigo "), + ("#A0522D", "Sienna"), + ("#FFD700", "Gold"), ("#F4A460", "SandyBrown"), ("#DAA520", "GoldenRod"), ("#DA70D6", "Orchid"), + ("#E6E6FA", "Lavender"), + ("#5F9EA0", "CadetBlue"), ("#D2691E", "Chocolate"), ("#66CDAA", "MediumAquaMarine"), ("#6B8E23", "OliveDrab"), + ("#A9A9A9", "DarkGray"), ("#BDB76B", "DarkKhaki"), ("#696969", "DimGray"), ("#B0C4DE", "LightSteelBlue"), + ("#191970", "MidnightBlue"), ("#FFE4C4", "Bisque"), ("#6A5ACD", "SlateBlue"), ("#EE82EE", "Violet"), + ("#8B4513", "SaddleBrown"), ("#FF7F50", "Coral"), ("#008000", "MidGreen"), ("#DB7093", "PaleVioletRed"), + ("#C0C0C0", "Silver"), + ("#E0FFFF", "LightCyan"), ("#9370DB", "MediumPurple"), ("#FF8C00", "DarkOrange"), + ("#00FA9A", "MediumSpringGreen"), + ("#E9967A", "DarkSalmon"), ("#778899", "LightSlateGray"), ("#9932CC", "DarkOrchid"), + ("#EEE8AA", "PaleGoldenRod"), + ("#F8F8FF", "GhostWhite"), ("#FFA07A", "LightSalmon"), ("#ADD8E6", "LightBlue"), ("#D8BFD8", "Thistle"), + ("#FFE4E1", "MistyRose"), ("#FFDEAD", "NavajoWhite"), ("#40E0D0", "Turquoise"), ("#90EE90", "LightGreen"), + ("#B22222", "FireBrick"), ("#008080", "Teal"), ("#F0FFF0", "HoneyDew"), ("#FFFACD", "LemonChiffon"), + ("#FFF5EE", "SeaShell"), + ("#F5F5DC", "Beige"), ("#DCDCDC", "Gainsboro"), ("#FA8072", "Salmon"), ("#8B008B", "DarkMagenta"), + ("#FFB6C1", "LightPink"), + ("#708090", "SlateGray"), ("#87CEFA", "LightSkyBlue"), ("#FFEFD5", "PapayaWhip"), ("#D2B48C", "Tan"), + ("#FFFFF0", "Ivory"), + ("#F0FFFF", "Azure"), ("#F5DEB3", "Wheat"), ("#00008B", "DarkBlue"), ("#FFDAB9", "PeachPuff"), + ("#8B0000", "DarkRed"), + ("#FAF0E6", "Linen"), ("#B0E0E6", "PowderBlue"), ("#FFE4B5", "Moccasin"), ("#F5F5F5", "WhiteSmoke"), + ("#FFF8DC", "Cornsilk"), + ("#FFFAFA", "Snow"), ("#FFF0F5", "LavenderBlush"), ("#FFEBCD", "BlanchedAlmond"), ("#F0F8FF", "AliceBlue"), + ("#FAEBD7", "AntiqueWhite"), ("#FDF5E6", "OldLace"), ("#FAFAD2", "LightGoldenRodYellow"), + ("#F5FFFA", "MintCream"), + ("#FFFAF0", "FloralWhite"), ("#7CFC00", "LawnGreen"), ("#663399", "RebeccaPurple")) codes = {} for i, pair in enumerate(hex_name): codes[pair[0]] = i low = pair[1].lower() codes[low] = i - codes[low.replace("gray","grey")] = i + codes[low.replace("gray", "grey")] = i codes["green"] = 2 codes["fuchsia"] = 4 codes["aqua"] = 6 @@ -72,7 +88,7 @@ class ColorMap(object): try: return int(colortext) except ValueError: - return ColorMap.codes.get(colortext.lower().replace("_",""),-1) + return ColorMap.codes.get(colortext.lower().replace("_", ""),-1) @staticmethod def check_hex(code): @@ -96,7 +112,6 @@ class ColorMap(object): return -1 - def get_vars(main, time): result = {} @@ -112,10 +127,13 @@ def get_vars(main, time): vars.append(vars[0]) if len(vars) == 3: vars.append("") - name, unit, label, color = vars + if len(vars) == 4: + vars.append("") # exact flag + name, unit, label, color, continuous = vars + continuous = int(continuous) if continuous else 0 if not unit in result: - result[unit] = dict(tag = unit, unit = unit.split("_")[0], curves=Dict()) - result[unit]["curves"][name] = dict(name=name, label=label, color=color) + result[unit] = dict(tag=unit, unit=unit.split("_")[0], curves=Dict()) + result[unit]["curves"][name] = dict(name=name, label=label, color=color, continuous=continuous) for unit, curvegroup in result.items(): color_set = set() @@ -137,14 +155,19 @@ def get_vars(main, time): color_set.add(c) curve["original_color"] = col curve["color"] = ColorMap.to_hex(c) - c = 1 # omit white + c = 1 # omit white for curve in auto_curves: - while c in color_set: c += 1 # find unused color + while c in color_set: + c += 1 # find unused color curve["color"] = ColorMap.to_hex(c) c += 1 return result -def get_curves(main, keys, timerange, show_empty=True): - curves = main.get_curves(keys, get_abs_time(*timerange), maxpoints=500) +def get_curves(main, keys, timerange, cut_begin=True): + curves = main.get_curves(keys, get_abs_time(*timerange), maxpoints=500, cut_begin=cut_begin) + #if 'tt:target' in curves: + # print('---') + # print(curves['tt:target'].fmtm()) + # print('TT', curves['tt:target'].for_json()[-5:]) return {k: c.for_json() for k, c in curves.items()} diff --git a/seaweb_hist.py b/seaweb_hist.py index e98e7e8..26d8959 100755 --- a/seaweb_hist.py +++ b/seaweb_hist.py @@ -64,7 +64,7 @@ def get_update(path=None): @flask.stream_with_context def generator(): logging.info('UPDATE %s %s', client.id, socket.getfqdn(flask.request.remote_addr.split(':')[-1])) - msg = dict(type='id', id=client.id, title=instrument.title, device=instrument.device); + msg = dict(type='id', id=client.id, instrument=instrument.title, device=instrument.device); yield to_json_sse(msg) try: lastmsg = time.time() @@ -130,6 +130,7 @@ def reply(): try: id = kwargs.pop('id') client = instrument.clients[id] + print('PATH', path) msg = getattr(client, "w_" + path[1:])(**kwargs) except Exception as e: logging.error('%s', traceback.format_exc()) @@ -257,6 +258,7 @@ class Instrument: self.clients[client.id] = client return client + class SeaInstrument(Instrument): # convert SEA layout tag like "-W" to more meaningful name. # the code: 0: modifier, 1: enum name, 2: input element @@ -502,17 +504,18 @@ class SeaGraph: LIVE = 2 def __init__(self): - self.livemode = self.HISTORICAL + self.livemode = self.ACTUAL self.time = [0, 0] self.lastvalues = {} + self.variables = [] def graphpoll(self): if self.livemode == self.LIVE: self.time[1], = get_abs_time(0) else: - self.time[1] = self.time[0] # do not update + self.time[1] = self.time[0] # do not update if self.time[1] > self.time[0]: - result = get_curves(main_cache, self.variables, self.time, show_empty=False) + result = get_curves(main_cache, self.variables, self.time, cut_begin=False) self.strip_future(result) if int(self.time[1] / 60) != int(self.time[0] / 60): # update unchanged values @@ -533,14 +536,8 @@ class SeaGraph: self.lastvalues[var] = (endtime, lastx) def strip_future(self, result): - """strip future points (happens only on dummy test_day)""" - # if self.livemode == self.LIVE: - for c in result.values(): - while len(c): - lastt, lastx = c[-1] - if lastt <= self.time[1]: - break - c.pop() + """strip future points (implemented on dummy test_day)""" + pass # do nothing here def w_gettime(self, time): result = get_abs_time(*[float(t) for t in time.split(',')]) @@ -552,7 +549,8 @@ class SeaGraph: self.last_t = 0 start, end, now = get_abs_time(*time, 0) self.time = [start, end] - self.variables = variables.split(',') + oldvars = set(self.variables) + self.variables.extend((v for v in variables.split(',') if v not in oldvars)) self.livemode = self.ACTUAL if end >= now else self.HISTORICAL logging.info('LIVE %g %g %d %d', end, now, end >= now, self.livemode) # self.scanner = seagraph.NumericScanner(instrument.logger_dir, instrument.test_day) @@ -561,7 +559,7 @@ class SeaGraph: self.strip_future(result) logging.info('VARIABLES: %r %r %r', self.variables, start-now, end-now) for var, curve in list(result.items()): - logging.info(' %s %r len=%d', var, curve[-1][0] - curve[0][0], len(curve)) + logging.info(' %s %r len=%d', var, curve[-1][0] - curve[0][0] if len(curve) else None, len(curve)) self.complete_to_end(result, end) self.time[0] = self.time[1] # reduction not yet implemented @@ -575,8 +573,10 @@ class SeaGraph: logging.info('GotVARS %r', result) return result - def w_updategraph(self): - logging.info("UPD GRAPH %d", self.livemode) + def w_updategraph(self, variables=''): + if variables: + self.variables = variables.split(',') + logging.info("UPD GRAPH %d %r", self.livemode, self.variables) if self.livemode == self.HISTORICAL: return dict(type='accept-graph', live=False) else: @@ -658,7 +658,7 @@ class SeaClient(SeaGraph): class DummyClient(SeaGraph): - async = set(('id','update','redraw','command','reply','graph-update','graph-redraw')) + asynch = set(('id','update','redraw','command','reply','graph-update','graph-redraw')) def __init__(self, host_port): self.linesocket = tcp_lineserver.LineClient(host_port) @@ -678,7 +678,7 @@ class DummyClient(SeaGraph): line = self.linesocket.get_line() if line != None: msg = json.loads(line) - if msg.type in self.async: + if msg.type in self.asynch: t = 0 # print 'PUSH',msg, replytype self.queue.append(msg) @@ -693,6 +693,12 @@ class DummyClient(SeaGraph): logging.error('REPLY MISMATCH %s %s <> %s', command, replytype, msg.type) return msg + def strip_future(self, result): + for c in result.values(): + while len(c): + if c[-1][0] <= self.time[1]: + break + c.pop() def w_getblock(self, path): return self.cmd_reply(dict(type='getblock', path=path, id=self.id), 'draw') @@ -718,7 +724,7 @@ class DummyClient(SeaGraph): messages = [] if line: msg = json.loads(line) - if msg.type in self.async: + if msg.type in self.asynch: messages.append(msg) else: self.syncreply.append(msg) @@ -761,7 +767,7 @@ class SecopMsg: self.par = sl[1] if len(sl) > 2: self.value = json.loads(' '.join(sl[2:])) - self.async = self.type in ('update', 'error_update') + self.asynch = self.type in ('update', 'error_update') def __repr__(self): value = repr(self.value) @@ -828,15 +834,15 @@ class SecopClient(SeaGraph): msg = self.syncreply.pop(0) break line = self.linesocket.get_line() - if line != None: + if line is not None: msg = SecopMsg(line) - if msg.async: + if msg.asynch: self.consolequeue.append(dict(type='reply',line=line,origin='async')) else: self.consolequeue.append(dict(type='reply',line=line,origin='other')) if self.out: self.out.write("<"+line+"\n") #print '<', msg.type, msg.par - if msg.async and replytype != msg.type + "=" + msg.par: + if msg.asynch and replytype != msg.type + "=" + msg.par: t = 0 self.queue.append(msg) else: @@ -895,31 +901,41 @@ class SecopClient(SeaGraph): if not command: return dict(type='accept-command') cmd = "change " + command - return self.cmd_reply(cmd, 'changed ' + command.split(' ')[0]) + self.cmd_reply(cmd, 'changed ' + command.split(' ')[0]) + return dict(type='accept-command') def poll(self): + messages = [] if self.consolequeue: - messages = self.consolequeue + messages.extend(self.consolequeue) self.consolequeue = [] - return messages if self.queue: - messages = convert_event(self.queue) + messages.extend(convert_event(self.queue)) self.queue = [] - return messages - line = self.linesocket.get_line() - # logging.info('poll %s', line) - if line: - if self.out: self.out.write("<"+line+"\n") + while True: + line = self.linesocket.get_line() + if not line: + break + # logging.info('poll %s', line) + if self.out: + self.out.write("<"+line+"\n") msg = SecopMsg(line) - if msg.async: # do not flood console with updates - self.consolequeue.append(dict(type='reply',line=line,origin='async')) + if msg.asynch: # do not flood console with updates + if msg.par == 'tt:target': + print(msg) + self.consolequeue.append(dict(type='reply', line=line, origin='async')) else: - self.consolequeue.append(dict(type='reply',line=line,origin='other')) - # logging.info('GOT MSG %r %r', msg.async, convert_event(SecopMsg(line))) - if msg.async: - return convert_event(SecopMsg(line)) - self.syncreply.append(msg) - return [] + self.consolequeue.append(dict(type='reply', line=line, origin='other')) + # logging.info('GOT MSG %r %r', msg.asynch, convert_event(SecopMsg(line))) + if msg.asynch: + messages.extend(convert_event(SecopMsg(line))) + else: + self.syncreply.append(msg) + # graph messages + msg = self.graphpoll() + if msg: + messages.append(msg) + return messages def info(self): return ["na"] @@ -939,6 +955,7 @@ class SecopInstrument(Instrument): def newClient(self): cl = SecopClient(self.host_port) self.device = cl.description['equipment_id'] + print('EQU', self.device) logging.info('init done %s %s', self.host_port, self.device) return self.register(cl) @@ -989,8 +1006,8 @@ if __name__ == '__main__': instrument_config = instrument_list[inst_name] - frd = FrappyReader('/home/l_samenv/sea/%s' % inst_name, gevent=gevent) - main_cache = MainCache('/home/l_samenv/histreader/%s_hist' % inst_name, frd, gevent=gevent) + frd = FrappyReader('/Users/zolliker/frappyhist/%s' % inst_name, gevent=gevent) + main_cache = MainCache('/Users/zolliker/frappyhist/%s_hist' % inst_name, frd, gevent=gevent) # logging.basicConfig(filename=inst_name+".log", filemode='w', level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') diff --git a/tcp_lineserver.py b/tcp_lineserver.py index 78d171b..9453b14 100644 --- a/tcp_lineserver.py +++ b/tcp_lineserver.py @@ -5,6 +5,7 @@ import re import circularlog import logging + class LineHandler(asyncore.dispatcher_with_send): def __init__(self, sock): @@ -38,6 +39,7 @@ class LineHandler(asyncore.dispatcher_with_send): ''' self.send_line("> " + line) + class LineServer(asyncore.dispatcher): def __init__(self, host, port, lineHandlerClass): @@ -58,9 +60,11 @@ class LineServer(asyncore.dispatcher): def loop(self): asyncore.loop() + class Disconnected(Exception): pass - + + class LineClient(object): def __init__(self, host_port, announcement=None, filter_ascii=False, ridername="r"): @@ -122,7 +126,8 @@ class LineClient(object): def close(self): self.socket.close() self.connected = False - + + if __name__ == "__main__": server = LineServer("localhost", 9999, LineHandler) server.loop()