Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<!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> -->
<!-- <script src="js/mqtt.min.js"></script> -->
<script src="js/paho/paho-mqtt.js"></script>
</head>
<body>
<div class='container'>
<h1>OhmPi Acquisition Board</h1>
<!-- nb stacks, on-time -->
<button id="update_settingsBtn" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#exampleModal">Settings</button>
<button id='runBtn' type="button" class="btn btn-primary">▶</button>
<button id='stopBtn' type="button" class="btn btn-warning">◼</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 secondStart
</label>
</div>
<div id='output'>Status: idle</div>
<!-- Pseudo section -->
<select id='surveySelect' class='custom-select'>
</select>
<input id="cmin" type="number" value="0"/>
<input id="cmax" type="number" value="150"/>
<button id="capplyBtn" type="button" class="btn btn-info">Apply</button>
<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>
<!-- trace figure -->
<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>
<!-- RS check -->
<button id="rsBtn" type="button" class="btn btn-info">Check contact resistance</button>
<button id="rsClearBtn" type="button" class="btn btn-info">Clear plot</button>
<div id="rs"></div>
<!-- Additional buttons -->
<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>
<div class="form-group row">
<label for="sequence" class="col-sm-2 col-form-label">Sequence</label>
<div class="col-sm-10">
<input type="file" class="form-control" id="sequence">
</div>
</div>
<div class="form-group row">
<label for="elecSpacing" class="col-sm-2 col-form-label">Electrode spacing [m]</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="elecSpacing", 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>
<button id="restartBtn" type="button" class="btn btn-danger">Restart</button>
<button id="shutdownBtn" type="button" class="btn btn-danger">Shutdown</button>
<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 serverUrl = 'http://' + window.location.host
console.log('serverUrl =', serverUrl)
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
let commands = {} // store commands and their id
let callbacks = {} // store callback (might not be needed)
// function with MQTT
let topic = 'ohmpi_0001' // we could change this through a drop-down to connect to a different ohmpi
let topic_ctrl = topic + '/ctrl'
let topic_exec = topic + '/exec'
let topic_data = topic + '/data'
let hostname = location.hostname
let port = 9001
let clientId = 'ohmpi_0001_html'
let message = null
// create client
client = new Paho.MQTT.Client(hostname, port, clientId);
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
client.connect({onSuccess:onConnect});
function onConnect() {
console.log("onConnect")
client.subscribe(topic_data)
client.subscribe(topic_exec)
// send welcome message
message = new Paho.MQTT.Message("Hello from index.html")
message.destinationName = topic_ctrl
client.send(message)
}
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0)
console.log("onConnectionLost:" + responseObject.errorMessage)
}
function onMessageArrived(message) {
console.log("onMessageArrived:" + message.payloadString)
let payload = message.payloadString
if (message.topic == topic_data) {
// process data
msg = payload // for accessing the variable from the console
console.log('DATA', payload)
let ddic = JSON.parse(payload.split('INFO:')[1])
// check cmd_id is any
processData(ddic)
// usually these don't have a cmd_id so we are not sure when
} else if (message.topic == topic_exec) {
// display it in the log
console.log('EXEC LOG:', payload)
// let response = JSON.parse(message.payloadString)
// console.log('response=', response)
// // check ID of message against our dictionnary of callback
// let cmd_id = response['cmd_id']
// if (callbacks.hasOwnProperty(cmd_id)) {
// console.log('++ execute callback')
// callbacks[cmd_id](response['content']) // execute callback
// }
} catch (e) {
console.log(e)
}
// client.disconnect()
}
// useful functions
function generateUniqSerial() {
return 'xxxx-xxxx-xxx-xxxx'.replace(/[x]/g, (c) => {
const r = Math.floor(Math.random() * 16);
return r.toString(16);
});
}
// sending commands to the OhmPi
function sendCommand(query, callback=null) {
// dic in the form: {'cmd': 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)
*/
// generate a unique command id to be associated with the commands
let uuid = generateUniqSerial()
commands[uuid] = query
callbacks[uuid] = callback // store the callback to be processed later when message arrives
let payload = '{"cmd_id": "' + uuid + '",' + query.slice(1)
console.log('sendCommand()', payload)
message = new Paho.MQTT.Message(payload)
message.destinationName = topic_ctrl
client.send(message)
}
// run button
function runBtnFunc() {
sendCommand('{"cmd": "run_multiple_sequences"}', function(x) {
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
console.log(x['status'])
if (x['status'] == 'running') {
output.innerHTML = 'Status: measuring...'
}
})
}
let runBtn = document.getElementById('runBtn')
runBtn.addEventListener('click', runBtnFunc)
// interrupt button
function stopBtnFunc() {
sendCommand('{"cmd": "interrupt"}', 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)
// define callback to send settings to Pi
function configCallback() {
sendCommand(JSON.stringify({
'cmd': 'update_settings',
'kwargs': {
'config': formVals
}
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
}), function(x) {
console.log('update_settings', x)
})
}
// deal with the potential file containing the sequence
// https://stackoverflow.com/questions/19038919/is-it-possible-to-upload-a-text-file-to-input-in-html-js
if (!window.FileReader) {
alert('Your browser is not supported');
return false;
}
let input = document.getElementById('sequence')
if (input.files.length) {
const reader = new FileReader()
reader.readAsText(input.files[0])
reader.addEventListener('load', () => {
formVals['sequence'] = reader.result
console.log('file==', reader.result)
configCallback()
}, false)
} else {
console.log('no sequence uploaded')
formVals['sequence'] = ''
configCallback()
}
}
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]',
cmin: 0,
cmax: 100,
}
}
}
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)
// 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)
// compute app res (assumping flat, line survey)
let xpos = []
let ypos = []
let app = []
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)
let am = Math.abs(a[i] - m[i])
let bm = Math.abs(b[i] - m[i])
let an = Math.abs(a[i] - n[i])
let bn = Math.abs(a[i] - n[i])
let K = (2*Math.PI)/((1/am)-(1/an)-(1/an)+(1/bn))
app.push(df['rho'][i]*-K)
}
console.log(app)
// update the trace and redraw the figure
trace['x'] = xpos
trace['y'] = ypos
trace['marker']['color'] = app
trace['marker']['cmax'] = document.getElementById('cmax').value
trace['marker']['cmin'] = document.getElementById('cmin').value
Plotly.redraw('gd')
}
}
let surveySelect = document.getElementById('surveySelect')
// bar chart for contact resistance
let rsdata = []
let rslayout = {
title: 'Contact resistances',
yaxis: {
title: 'Resistance [kOhm]'
},
xaxis: {
title: 'Consecutive electrodes'
}
}
Plotly.newPlot('rs', rsdata, rslayout)
// run RS check
function rsBtnFunc() {
sendCommand('{"cmd": "rsCheck"}', function (res) {
// update the bar plot
rsdata.push({
x: res['data']['AB'],
y: res['data']['res'],
name: 'RS',
type: 'bar'
})
Plotly.redraw('rs')
})
}
let rsBtn = document.getElementById('rsBtn')
rsBtn.addEventListener('click', rsBtnFunc)
// clear RS graph
function rsClearBtnFunc() {
rsdata = []
Plotly.newPlot('rs', rsdata, rslayout)
}
let rsClearBtn = document.getElementById('rsClearBtn')
rsClearBtn.addEventListener('click', rsClearBtnFunc)
// getData
function getData() {
sendCommand(JSON.stringify({
'cmd': 'get_data',
'survey_names': Object.keys(data).slice(0, -1)
// last survey is often partial so we download it again
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
}), console.log('processData(ddic)')
)
}
// processData
function processData(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()
// 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)
// 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))
// 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')
}
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
let getDataBtn = document.getElementById('getDataBtn')
getDataBtn.addEventListener('click', getData)
// apply new colorscale
let capplyBtn = document.getElementById('capplyBtn')
capplyBtn.addEventListener('click', function() {
surveySelectFunc({'target': surveySelect})
})
// 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('{"cmd": "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)
// shutdown Pi
function shutdownBtnFunc() {
sendCommand('{"cmd": "shutdown"}', function(x) {
console.log('shuting down...')
})
}
let shutdownBtn = document.getElementById('shutdownBtn')
shutdownBtn.addEventListener('click', shutdownBtnFunc)
// restart Pi
function restartBtnFunc() {
sendCommand('{"cmd": "restart"}', function(x) {
console.log('rebooting...')
})
}
let restartBtn = document.getElementById('restartBtn')
restartBtn.addEventListener('click', restartBtnFunc)
// invert data
// function invertBtnFunc() {
// sendCommand('{"cmd": "invert"}', function(x) {
// console.log('inversion results', x)
// })
// }
// let invertBtn = document.getElementById('invertBtn')
// invertBtn.addEventListener('click', invertBtnFunc)
// download data
function downloadBtnFunc() {
sendCommand('{"cmd": "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>