253 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!DOCTYPE html>
 | |
| <html lang="en">
 | |
| <head>
 | |
|     <meta charset="utf-8">
 | |
|     <title>SwimTrainer</title>
 | |
| 
 | |
|     <!-- plot.ly -->
 | |
|     <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
 | |
| 
 | |
|     <!-- chart.js -->
 | |
|     <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
 | |
| 
 | |
|     <!-- Materialize -->
 | |
|     <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 | |
|     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
 | |
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
 | |
|     <!-- MsgPack -->
 | |
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/msgpack5/4.2.0/msgpack5.js"></script>
 | |
| 
 | |
|     <link rel="manifest" href="swimtrainer.webmanifest">
 | |
| </head>
 | |
| 
 | |
| 
 | |
| <body>
 | |
| 
 | |
| <nav class="blue " role="navigation">
 | |
|     <div class="nav-wrapper container"><a id="logo-container" href="#" class="brand-logo">SwimTrainer</a>
 | |
|     </div>
 | |
| </nav>
 | |
| 
 | |
| 
 | |
| 
 | |
| <div class="container">
 | |
|     <div class="row">
 | |
|         <div class="row">
 | |
|             <div class="col s4 center-align">
 | |
|                 <h5>Bahnen</h5>
 | |
|                 <h1><span id="lanes"></span></h1>
 | |
|             </div>
 | |
|             <div class="col s4 center-align">
 | |
|                 <h5>Züge</h5>
 | |
|                 <h1><span id="peaks"></span></h1>
 | |
|             </div>
 | |
|             <div class="col s4 center-align">
 | |
|                 <a class="waves-effect waves-light btn-large" onclick="reload()">Neu laden</a>
 | |
|             </div>
 | |
|         </div>
 | |
|     </div>
 | |
| 
 | |
|     <div class="row">
 | |
|         <div class="col s12">
 | |
|             <canvas id="plotCanvas"></canvas>
 | |
|         </div>
 | |
|     </div>
 | |
| 
 | |
|     <div class="row">
 | |
|         <div class="col s3">
 | |
|         </div>
 | |
|         <div class="col s6">
 | |
|             <div class="container">
 | |
|                 <div class="row">
 | |
|                     <div class="col s6"><label>Seconds</label></div>
 | |
|                     <div class="col s6"> <input type="number" id="seconds" min="30" max="3600" value="120">  </div>
 | |
|                 </div>
 | |
|                 <div class="row">
 | |
|                     <div class="col s6"><label>Threshold</label></div>
 | |
|                     <div class="col s6"> <input type="number" id="threshold"  min="100" max="60000" value="2000">  </div>
 | |
|                 </div>
 | |
|                 <div class="row">
 | |
|                     <div class="col s6"><label>y max</label></div>
 | |
|                     <div class="col s6"> <input type="number" id="ymax"  min="1000" max="60000" value="4000">  </div>
 | |
|                 </div>
 | |
|                 <div class="row">
 | |
|                     <div class="btn red white-text waves-effect waves-light" id="clickMe" type="button" onclick="save();">
 | |
|                         <i class="material-icons left">save</i>
 | |
|                         Speichern
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
| 
 | |
|     </div>
 | |
| </div>
 | |
| 
 | |
| <script>
 | |
|     const url = 'http://smartswim/api/session/data';
 | |
| 
 | |
|     // ------------------ MsgPack --------------------------------------------------------------
 | |
|     var msgpack = msgpack5();
 | |
|     msgpack.registerDecoder(0xd1, function (byteArr) {
 | |
|         function typedArrayToBuffer(array) {
 | |
|             return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset)
 | |
|         }
 | |
| 
 | |
|         return new Int16Array(typedArrayToBuffer(byteArr));
 | |
|     });
 | |
| 
 | |
|     function countPeaks(array, threshold=500)
 | |
|     {
 | |
|         var result = 0;
 | |
|         var lastMin = 0;
 | |
|         for(var i = 1; i < array.length - 2; i++)
 | |
|         {
 | |
|             var current = array[i].y;
 | |
|             var last = array[i-1].y;
 | |
|             var next = array[i+1].y;
 | |
| 
 | |
|             if(current > next && current > last && (current - lastMin) > threshold) {
 | |
|                 result += 1;
 | |
|                 lastMin = array[i].y;
 | |
|             }
 | |
|             lastMin = Math.min(lastMin, array[i].y);
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     // ------------------ Plot data -------------------------------------------------------------
 | |
| 
 | |
| 
 | |
|     var ctx = document.getElementById('plotCanvas').getContext('2d');
 | |
|     var chart = new Chart(ctx, {
 | |
|         type: 'line',
 | |
|         data: {
 | |
|           datasets: [{
 | |
|               data: [],
 | |
|               backgroundColor: '#2196f388',
 | |
|               color: 'blue'
 | |
|           }]
 | |
|         },
 | |
|         options: {color: ['red']}
 | |
|     });
 | |
| 
 | |
| 
 | |
|     var appendToPlotArray = function (typedTimeArr, typedValueArr, currentTime) {
 | |
|         var plotArray = chart.data.datasets[0].data;
 | |
|         // remove last element, pop on empty is ok
 | |
|         plotArray.pop();
 | |
| 
 | |
|         for (var i = 0; i < typedValueArr.length; ++i) {
 | |
|             plotArray.push({x: typedTimeArr[i], y: typedValueArr[i]});
 | |
|         }
 | |
|         plotArray.push({x: currentTime, y: plotArray[plotArray.length - 1].y});
 | |
| 
 | |
|         var secondsToShow = parseInt( document.getElementById("seconds").value );
 | |
|         var yMax = parseInt( document.getElementById("ymax").value );
 | |
| 
 | |
|         chart.options = {
 | |
|             legend: {
 | |
|                 display: false
 | |
|             },
 | |
|             elements: {
 | |
|                 point: {
 | |
|                     radius: 2
 | |
|                 },
 | |
|                 line: {
 | |
|                     tension: 0 // disables bezier curves
 | |
|                 }
 | |
|             },
 | |
|             scales: {
 | |
|                 xAxes: [{
 | |
|                     ticks: {
 | |
|                         min: plotArray[plotArray.length - 1].x - secondsToShow * 10,
 | |
|                         max: plotArray[plotArray.length - 1].x
 | |
|                     },
 | |
|                     type: 'linear',
 | |
|                     display: false
 | |
|                 }],
 | |
|                 yAxes: [{
 | |
|                     ticks: {
 | |
|                         min:  0,
 | |
|                         max: yMax
 | |
|                     },
 | |
|                     type: 'linear'
 | |
|                 }],
 | |
|             }
 | |
|         };
 | |
|         chart.data.datasets[0].data = plotArray;
 | |
|         chart.update();
 | |
|     };
 | |
| 
 | |
|     var reload = function() {
 | |
|         chart.data.datasets[0].data = [];
 | |
|     };
 | |
|     /*
 | |
|     var appendToPlot = function (typedTimeArr, typedValueArr, currentTime)
 | |
|     {
 | |
|         return;
 | |
|         // remove last element, pop on empty is ok
 | |
|         plotData.x.pop();
 | |
|         plotData.y.pop();
 | |
| 
 | |
|         var timeArr = Array.prototype.slice.call(typedTimeArr);
 | |
|         var valueArr = Array.prototype.slice.call(typedValueArr);
 | |
|         plotData.x = plotData.x.concat(timeArr);
 | |
|         plotData.y = plotData.y.concat(valueArr);
 | |
| 
 | |
|         // add last element
 | |
|         plotData.x.push(currentTime);
 | |
|         plotData.y.push(plotData.y[plotData.y.length - 1]);
 | |
| 
 | |
|         var plotObj = document.getElementById('plot');
 | |
|         Plotly.purge(plotObj);
 | |
|         Plotly.plot(plotObj, [plotData]);
 | |
|     };*/
 | |
| 
 | |
|     var updateRunning = false;
 | |
|     var update = function ()
 | |
|     {
 | |
|         if( updateRunning === true ) {
 | |
|             //console.log("Skipping update, because update is running");
 | |
|             return;
 | |
|         }
 | |
|         var oReq = new XMLHttpRequest();
 | |
|         var startIndex = chart.data.datasets[0].data.length;
 | |
|         if (startIndex > 0) {
 | |
|             startIndex -= 1;
 | |
|         }
 | |
| 
 | |
|         oReq.open("GET", url + "?startIndex=" + startIndex, true);
 | |
|         oReq.responseType = "arraybuffer";
 | |
|         //console.log("-> Query, startIndex=", startIndex);
 | |
|         oReq.onload = function (oEvent) {
 | |
|             var arrayBuffer = oReq.response; // Note: not oReq.responseText
 | |
|             if (arrayBuffer) {
 | |
|                 var decoded = msgpack.decode(arrayBuffer);
 | |
|                 //console.log("-> Finished query", startIndex, "#old:", chart.data.datasets[0].data.length,
 | |
|                 //            "#new:", decoded['values'].length);
 | |
|                 appendToPlotArray(decoded['timestamps'], decoded['values'], decoded['currentTime']);
 | |
|                 //console.log("-> After unpack",  chart.data.datasets[0].data.length);
 | |
|                 var peakThreshold = parseInt( document.getElementById("threshold").value );
 | |
|                 var peaks = countPeaks(chart.data.datasets[0].data, peakThreshold);
 | |
|                 document.getElementById("peaks").textContent = peaks;
 | |
|                 document.getElementById("lanes").textContent = (peaks / 30).toFixed(1);
 | |
| 
 | |
|             }
 | |
|             updateRunning = false;
 | |
|         };
 | |
|         oReq.send(null);
 | |
|         updateRunning = true;
 | |
|     };
 | |
|     update();
 | |
| 
 | |
|     setInterval(update, 1000);
 | |
| </script>
 | |
| 
 | |
| </body>
 | |
| </html>
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |