<!DOCTYPE html> <html> <head> <meta charset="utf8"/> <title>OhmPi Acquisition Board</title> <link rel="shortcut icon" type="image/jpg" href="logo_ohmpi.jpg"/> <!-- dependencies (need to be local as no internet in AP mode)--> <script src="js/plotly-basic-2.8.3.min.js"></script> <script src="js/jquery-3.4.1.min.js"></script> <link type="text/css" href="css/bootstrap.min.css" rel="stylesheet"> <!-- <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> --> <!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous"> --> <!-- <script src="js/danfojs/bundle.min.js"></script> --> </head> <body> <div class='container'> <h1>OhmPi Acquisition Board</h1> <!-- nb stacks, on-time --> <button id="setConfigBtn" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#exampleModal">Settings</button> <button id='startBtn' type="button" class="btn btn-primary">Start</button> <button id='stopBtn' type="button" class="btn btn-warning">Stop</button> <!-- upload button for csv which display the table ABMN --> <button id="removeDataBtn" type="button" class="btn btn-danger">Clear data</button> <button id="getDataBtn" type="button" class="btn btn-info">Get data</button> <div class="form-check"> <input id="dataRetrievalCheck" class="form-check-input" type="checkbox" value=""> <label class="form-check-label" for="dataRetrievalCheck"> Automaticaly get data every 1 second </label> </div> <div id='output'>Status: idle</div> <select id='surveySelect' class='custom-select'> </select> <div id="gd"></div> <div class="mb3 row"> <label for="quadSelect">Quadrupole:</label> <div class="col-sm-10"> <select id='quadSelect' class='custom-select'></select> </div> </div> <button id="addTraceBtn" type="button" class="btn btn-info">Add trace</button> <button id="removeTracesBtn" type="button" class="btn btn-info">Remove all traces</button> <div id="ts"></div> <button id="downloadBtn" type="button" class="btn btn-primary">Download data</button> <!-- <button id="invertBtn" type="button" class="btn btn-primary">Invert</button> --> <a id="download"></a> <!-- Modal for configuration --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">OhmPi configuration</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <form> <div class="form-group row"> <label for="nbElectrodes" class="col-sm-2 col-form-label">Nb electrodes</label> <div class="col-sm-10"> <input type="number" class="form-control-number" id="nbElectrodes" value="64"> </div> </div> <div class="form-group row"> <label for="injectionDuration" class="col-sm-2 col-form-label">Injection duration [s]</label> <div class="col-sm-10"> <input type="number" class="form-control-number" id="injectionDuration" value="0.2"> </div> </div> <div class="form-group row"> <label for="nbMeasurements" class="col-sm-2 col-form-label">Nb Measurements</label> <div class="col-sm-10"> <input type="number" class="form-control-number" id="nbMeasurements" value="1"> </div> </div> <div class="form-group row"> <label for="sequenceDelay" class="col-sm-2 col-form-label">Sequence delay [s]</label> <div class="col-sm-10"> <input type="number" class="form-control-number" id="sequenceDelay" value="100"> </div> </div> <div class="form-group row"> <label for="nbStack" class="col-sm-2 col-form-label">Nb stack</label> <div class="col-sm-10"> <input type="number" class="form-control-number" id="nbStack" value="1"> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button id="saveConfigBtn" type="button" data-dismiss="modal" class="btn btn-primary">Save</button> </div> </div> </div> </div> <footer>v0.2.0</footer> </div> <script type="text/javascript"> //let serverUrl = 'http://10.3.141.1:8080' let serverUrl = 'http://0.0.0.0:8080' //let serverUrl = 'http://localhost:8080' let output = document.getElementById('output') let data = {} // hold data of all surveys let interv = null // hold interval for automatic data retrieval let quads = [] // available quadrupoles for time-serie figure let squads = [] // selected quadrupoles for time-serie figure // useful functions function sendCommand(query, callback=null) { // dic in the form: {'command': X, ...} as JSON if (callback == null) { function callback(x) { console.log('default callback:', x) } } let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == 4) { if (xhr.status == 200) { callback(JSON.parse(xhr.response)) } } } xhr.open('POST', serverUrl) xhr.setRequestHeader('Content-Type', 'application/json') xhr.send(query) } // start button function startBtnFunc() { sendCommand('{"command": "start"}', function(x) { console.log(x['status']) if (x['status'] == 'running') { output.innerHTML = 'Status: measuring...' } }) } let startBtn = document.getElementById('startBtn') startBtn.addEventListener('click', startBtnFunc) // stop button function stopBtnFunc() { sendCommand('{"command": "stop"}', function(x) { output.innerHTML = 'Status: ' + x['status'] clearInterval(interv) getData() }) } let stopBtn = document.getElementById('stopBtn') stopBtn.addEventListener('click', stopBtnFunc) // set configuration function saveConfigBtnFunc() { // collect values from modal let formVals = {} for (let field of ['nbElectrodes', 'injectionDuration', 'nbMeasurements', 'sequenceDelay', 'nbStack']) { formVals[field] = document.getElementById(field).value } console.log(formVals) sendCommand(JSON.stringify({ 'command': 'setConfig', 'config': formVals }), function(x) { console.log('setconfig:', x) }) } let saveConfigBtn = document.getElementById('saveConfigBtn') saveConfigBtn.addEventListener('click', saveConfigBtnFunc) // make pseudo plot var trace = { x: [], y: [], mode: 'markers', marker: { size: 40, color: [], colorbar: { title: 'App. res. [Ohm.m]' } } } let layout = { title: 'Pseudo-section', yaxis: { title: 'Pseudo-depth', autorange: 'reversed' }, xaxis: { title: 'X' } } Plotly.newPlot('gd', [trace], layout) // make time-serie plot let tdata = [] let layout2 = { title: 'Time-serie', yaxis: { title: 'App. res. [Ohm.m]' }, xaxis: { title: 'Sampling time' } } Plotly.newPlot('ts', tdata, layout2) // add trace to time-serie plot function addTraceBtnFunc() { let val = document.getElementById('quadSelect').value squads.push(val.split(', ')) tdata.push({ x: [], y: [], name: val, type: 'scatter' }) Plotly.newPlot('ts', tdata, layout2) getData() } let addTraceBtn = document.getElementById('addTraceBtn') addTraceBtn.addEventListener('click', addTraceBtnFunc) // remove all traces from time-serie plot function removeTracesBtnFunc() { squads = [] tdata = [] Plotly.newPlot('ts', tdata, layout2) } let removeTracesBtn = document.getElementById('removeTracesBtn') removeTracesBtn.addEventListener('click', removeTracesBtnFunc) // getData function getData() { sendCommand(JSON.stringify({ 'command': 'getData', 'surveyNames': Object.keys(data).slice(0, -1) // last survey is often partial so we download it again }), function(ddic) { // update status output.innerHTML = 'Status: ' + ddic['status'] // update data dic with new data data = { // destructuring assignement (magic! :o) ...data, ...ddic['data'] // value from second dic are preferred } // dropdown with number of surveys and +++ let surveyNames = Object.keys(data).sort() // callback function to draw the plot function surveySelectFunc(el) { let surveyName = el['target'].value let df = data[surveyName] if (df != undefined) { let a = df['a'] let b = df['b'] let m = df['m'] let n = df['n'] // let's assume electrodes are 1 m distance // compute pseudo-depth (assume no topo) let xpos = [] let ypos = [] for (let i = 0; i < a.length; i++) { let ab = (a[i] + b[i])/2 let mn = (m[i] + n[i])/2 let dist = Math.abs(ab - mn) xpos.push(Math.min(ab, mn) + dist/2) ypos.push(Math.sqrt(2)/2*dist) } // update the trace and redraw the figure trace['x'] = xpos trace['y'] = ypos trace['marker']['color'] = df['rho'] Plotly.redraw('gd') } } let surveySelect = document.getElementById('surveySelect') // remove listener as we will replace the choices surveySelect.removeEventListener('change', surveySelectFunc) surveySelect.innerHTML = '' // clearing all child nodes // add choices again for (let surveyName of surveyNames) { let option = document.createElement('option') option.innerText = surveyName option.value = surveyName surveySelect.appendChild(option) } // listener again surveySelect.addEventListener('change', surveySelectFunc) // plot last one by default surveySelect.value = surveyNames[surveyNames.length - 1] // call the function directly // (as progammatically chaging the value does not trigger the event) surveySelectFunc({'target': surveySelect}) // update list of quadrupoles if any if (quads.length == 0) { console.log('updating list of quadrupoles') let df = data[surveyNames[0]] let quadSelect = document.getElementById('quadSelect') quadSelect.innerHTML = '' for (let i = 0; i < df['a'].length; i++) { quad = [df['a'][i], df['b'][i], df['m'][i], df['n'][i]] quads.push(quad) let option = document.createElement('option') option.value = quad.join(', ') option.innerText = quad.join(', ') quadSelect.appendChild(option) } console.log('quads=', quads) } // update time-serie figure if (squads.length > 0) { // transform all surveyNames to datetime let xt = [] for (surveyName of surveyNames) { let a = surveyName.split('_').slice(-1)[0] xt.push(a.slice(0, 4) + '-' + a.slice(4, 6) + '-' + a.slice(6, 8) + ' ' + a.slice(9, 11) + ':' + a.slice(11, 13) + ':' + a.slice(13, 15)) } //console.log(xt) // create one new trace per selected quadrupole for (let k = 0; k < squads.length; k++) { squad = squads[k] let x = [] let y = [] for (let i = 0; i < surveyNames.length; i++) { df = data[surveyNames[i]] for (let j = 0; j < df['a'].length; j++) { if (df['a'][j] == squad[0] && df['b'][j] == squad[1] && df['m'][j] == squad[2] && df['n'][j] == squad[3]) { y.push(df['rho'][j]) x.push(xt[i]) break } } } // update trace dictionnary tdata[k]['x'] = x tdata[k]['y'] = y } //console.log(tdata) Plotly.redraw('ts') } }) } let getDataBtn = document.getElementById('getDataBtn') getDataBtn.addEventListener('click', getData) // checkbox interaction for data download function dataRetrievalCheckFunc(x) { if (x['target'].checked == true) { interv = setInterval(getData, 1000) // every 5s } else { clearInterval(interv) } } let dataRetrievalCheck = document.getElementById('dataRetrievalCheck') dataRetrievalCheck.addEventListener('change', dataRetrievalCheckFunc) // remove data function removeDataBtnFunc() { sendCommand('{"command": "removeData"}',function(x) { data = {} output.innerHTML = 'Status: ' + x['status'] + ' (all data cleared)' console.log('all data removed') }) } let removeDataBtn = document.getElementById('removeDataBtn') removeDataBtn.addEventListener('click', removeDataBtnFunc) // invert data // function invertBtnFunc() { // sendCommand('{"command": "invert"}', function(x) { // console.log('inversion results', x) // }) // } // let invertBtn = document.getElementById('invertBtn') // invertBtn.addEventListener('click', invertBtnFunc) // download data function downloadBtnFunc() { sendCommand('{"command": "download"}', function(x) { let dwl = document.getElementById('download') dwl.setAttribute('href', serverUrl + '/data.zip') dwl.setAttribute('download', 'data.zip') dwl.click() }) } let downloadBtn = document.getElementById('downloadBtn') downloadBtn.addEventListener('click', downloadBtnFunc) </script> <!-- Boostrap scripts (at the end of the page for faster loading time)--> <script src="js/bootstrap.bundle.min.js"></script> <!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous"></script> --> </body> </html>