diff --git a/README.md b/README.md index 25aeda550050520c27e9e0c7312d34fe0b2b9538..243403d4ef388883a3e09fbc844d2e7e3958e256 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ We strongly recommend users to create a virtual environment to run the code and * you should run you code within the virtual environment * to leave the virtual environment simply type: `deactivate` +To enable to Python module, we need to add the 'ohmpi' folder to the python path. This can be done by `source .env` or manually by adding `export PYTHONPATH=$PYTHONPATH:/home/<username>/OhmPi` to /home/<username>/.bashrc. + ## First run with four electrodes In the examples folder, you will find the code "simple_measurement.py". diff --git a/configs/config_mb_2023.py b/configs/config_mb_2023.py index 3b6b704a195a7930eab97492e552242aa08e4ad6..8bd970d8096ec3515ced1a1c9d11ea5e2aeabbc7 100644 --- a/configs/config_mb_2023.py +++ b/configs/config_mb_2023.py @@ -19,7 +19,7 @@ OHMPI_CONFIG = { HARDWARE_CONFIG = { 'ctl': {'model': 'raspberry_pi'}, - 'pwr': {'model': 'pwr_batt', 'voltage': 12.}, + 'pwr': {'model': 'pwr_batt', 'voltage': 12., 'interface_name': 'none'}, 'tx': {'model': 'mb_2023_0_X', 'voltage_max': 12., # Maximum voltage supported by the TX board [V] 'adc_voltage_max': 4800., # Maximum voltage read by the current ADC on the TX board [mA] diff --git a/configs/config_mb_2024_0_2.py b/configs/config_mb_2024_0_2.py index e90350deab5262485d623a7013ae297d49ddfca5..f99c214d235e821f4b55fca27db02d6b5bff2760 100644 --- a/configs/config_mb_2024_0_2.py +++ b/configs/config_mb_2024_0_2.py @@ -18,7 +18,7 @@ OHMPI_CONFIG = { HARDWARE_CONFIG = { 'ctl': {'model': 'raspberry_pi'}, - 'pwr': {'model': 'pwr_batt', 'voltage': 12.}, + 'pwr': {'model': 'pwr_batt', 'voltage': 12., 'interface_name': 'none'}, 'tx': {'model': 'mb_2024_0_2', 'voltage_max': 50., # Maximum voltage supported by the TX board [V] 'current_max': 4800, # Maximum voltage read by the current ADC on the TX board [mA] diff --git a/configs/config_mb_2024_0_2__1_mux_2024.py b/configs/config_mb_2024_0_2__1_mux_2024.py index e83edf46062718979a6f96fdb8c95dcd1585d9a7..a9a91e4d052bb426c4f5c9e3765578b0a7f73a19 100644 --- a/configs/config_mb_2024_0_2__1_mux_2024.py +++ b/configs/config_mb_2024_0_2__1_mux_2024.py @@ -18,7 +18,7 @@ OHMPI_CONFIG = { HARDWARE_CONFIG = { 'ctl': {'model': 'raspberry_pi'}, - 'pwr': {'model': 'pwr_batt', 'voltage': 12.}, + 'pwr': {'model': 'pwr_batt', 'voltage': 12., 'interface_name': 'none'}, 'tx': {'model': 'mb_2024_0_2', 'voltage_max': 50., # Maximum voltage supported by the TX board [V] 'current_max': 4800, # Maximum voltage read by the current ADC on the TX board [mA] diff --git a/configure_second_I2C_bus.sh b/configure_second_I2C_bus.sh new file mode 100644 index 0000000000000000000000000000000000000000..43e8b4509f750f3fb975bdec257e47c00b62bd92 --- /dev/null +++ b/configure_second_I2C_bus.sh @@ -0,0 +1,2 @@ +sudo apt-get install -y i2c-tools +echo -e "[all]\ndtoverlay=i2c-gpio,bus=4,i2c_gpio_delay_us=1,i2c_gpio_sda=22,i2c_gpio_scl=23" | sudo tee -a /boot/config.txt diff --git a/dev/test_mb_2023_0_mux.py b/dev/test_mb_2023_0_mux.py new file mode 100644 index 0000000000000000000000000000000000000000..4dfafdf74eeb36d7368fdfd1c67018929b840e5f --- /dev/null +++ b/dev/test_mb_2023_0_mux.py @@ -0,0 +1,117 @@ +import matplotlib +matplotlib.use('TkAgg') +from ohmpi.utils import change_config +change_config('../configs/config_mb_2023.py', verbose=False) +import importlib +import time +import logging +from ohmpi.config import HARDWARE_CONFIG + +stand_alone = False +part_of_hardware_system = False +within_ohmpi = True + +# Stand alone +if stand_alone: + + ctl_module = importlib.import_module(f'ohmpi.hardware_components.{HARDWARE_CONFIG["ctl"].pop("model")}') + pwr_module = importlib.import_module(f'ohmpi.hardware_components.{HARDWARE_CONFIG["pwr"].pop("model")}') + tx_module = importlib.import_module(f'ohmpi.hardware_components.{HARDWARE_CONFIG["tx"].pop("model")}') + rx_module = importlib.import_module(f'ohmpi.hardware_components.{HARDWARE_CONFIG["rx"].pop("model")}') + + ctl = ctl_module.Ctl() + HARDWARE_CONFIG['tx'].update({'ctl': ctl, 'exec_logger': ctl.exec_logger, 'soh_logger': ctl.soh_logger}) + HARDWARE_CONFIG['rx'].update({'ctl': ctl, 'exec_logger': ctl.exec_logger, 'soh_logger': ctl.soh_logger}) + HARDWARE_CONFIG['tx'].update({'connection': HARDWARE_CONFIG['tx'].pop('connection', + ctl.interfaces[ + HARDWARE_CONFIG['tx'].pop( + 'interface_name', 'i2c')])}) + HARDWARE_CONFIG['rx'].update({'connection': HARDWARE_CONFIG['rx'].pop('connection', + ctl.interfaces[ + HARDWARE_CONFIG['rx'].pop( + 'interface_name', 'i2c')])}) + + HARDWARE_CONFIG['pwr'].update({'connection': HARDWARE_CONFIG['pwr'].pop('connection', + ctl.interfaces[ + HARDWARE_CONFIG['pwr'].pop( + 'interface_name', None)])}) + + + rx = rx_module.Rx(**HARDWARE_CONFIG['rx']) + tx = tx_module.Tx(**HARDWARE_CONFIG['tx']) + pwr = pwr_module.Pwr(**HARDWARE_CONFIG['pwr']) + + # mux_ids = ['mux_02', 'mux_05'] + # for m,mux_id in enumerate(mux_ids): + # mux_module = importlib.import_module( + # f'ohmpi.hardware_components.{HARDWARE_CONFIG["mux"]["boards"][mux_id].pop("model")}') + + # MUX_CONFIG = HARDWARE_CONFIG['mux']['boards'][mux_id] + + # MUX_CONFIG.update({'ctl': ctl, 'connection': MUX_CONFIG.pop('connection', ctl.interfaces[ + # MUX_CONFIG.pop('interface_name', 'i2c_ext')]), 'exec_logger': ctl.exec_logger, + # 'soh_logger': ctl.soh_logger}) + # MUX_CONFIG.update({'id': mux_id}) + # mux = mux_module.Mux(**MUX_CONFIG) + + # # tx.polarity = 1 + # # time.sleep(1) + # # tx.polarity = 0 + # # mux.switch(elec_dict={'A': [1], 'B': [4], 'M': [2], 'N': [3]}, state='on') + # # time.sleep(1) + # # voltage = rx.voltage + # # current = tx.current + # # mux.switch(elec_dict={'A': [1], 'B': [4], 'M': [2], 'N': [3]}, state='off') + # # print(f'Resistance: {voltage / current :.2f} ohm, voltage: {voltage:.2f} mV, current: {current:.2f} mA') + # mux.reset() + # mux.test({'A': [i+8*m for i in range(1, 9)], 'B': [i+8*m for i in range(1, 9)], + # 'M': [i+8*m for i in range(1, 9)], 'N': [i+8*m for i in range(1, 9)]}, activation_time=.1) + # mux.reset() + +# mux as part of a OhmPiHardware system +if part_of_hardware_system: + from ohmpi.hardware_system import OhmPiHardware + print('Starting test of as part of an OhmPiHardware system.') + # mux_id = 'mux_03' + k = OhmPiHardware() + k.exec_logger.setLevel(logging.DEBUG) + # Test mux switching + k.reset_mux() + # k.switch_mux(electrodes=[1, 4, 2, 3], roles=['A', 'B', 'M', 'N'], state='on') + # time.sleep(1.) + # k.switch_mux(electrodes=[1, 4, 2, 3], roles=['A', 'B', 'M', 'N'], state='off') + # k.mux_boards[mux_id].test(activation_time=.4) + k.test_mux() + k.reset_mux() + +if within_ohmpi: + from ohmpi.ohmpi import OhmPi + # from ohmpi.plots import plot_exec_log + + print('Starting test with OhmPi.') + k = OhmPi() + # A, B, M, N = (32, 29, 31, 30) + k.reset_mux() + # k.test_mux(mux_id='mux_03') + # k._hw.switch_mux([A, B, M, N], state='on') + # k._hw.vab_square_wave(12.,1., cycles=2) + # k._hw.switch_mux([A, B, M, N], state='off') + # k._hw.calibrate_rx_bias() # electrodes 1 4 2 3 should be connected to a reference circuit + # k._hw.rx._bias = -1.38 + # print(f'Resistance: {k._hw.last_rho :.2f} ohm, dev. {k._hw.last_dev:.2f} %, rx bias: {k._hw.rx._bias:.2f} mV') + # k._hw._plot_readings() + A, B, M, N = (0, 0, 0, 0) + # k._hw.switch_mux([A, B, M, N], state='on') + # k._hw.vab_square_wave(12., cycle_duration=10., cycles=3) + # k._hw.switch_mux([A, B, M, N], state='off') + # print(f'OhmPiHardware Resistance: {k._hw.last_rho :.2f} ohm, dev. {k._hw.last_dev:.2f} %, rx bias: {k._hw.rx._bias:.2f} mV') + # k._hw._plot_readings() + print('using OhmPi') + d = k.run_measurement([A, B, M, N], injection_duration=1., nb_stack=2, duty_cycle=0.5) + print(d) + # k._hw._plot_readings() + print(f'OhmPiHardware: Resistance: {k._hw.last_resistance() :.2f} ohm, dev. {k._hw.last_dev():.2f} %, sp: {k._hw.sp:.2f} mV, rx bias: {k._hw.rx._bias:.2f} mV') + print(f'OhmPi: Resistance: {d["R [ohm]"] :.2f} ohm, dev. {d["R_std [%]"]:.2f} %, rx bias: {k._hw.rx._bias:.2f} mV') + k._hw._plot_readings(save_fig=False) + # plot_exec_log('ohmpi/logs/exec.log') +change_config('../configs/config_default.py', verbose=False) diff --git a/index-mqtt.html b/index-mqtt.html deleted file mode 100755 index 070d4fe13ea81700590c04d23493fbab8dcb512a..0000000000000000000000000000000000000000 --- a/index-mqtt.html +++ /dev/null @@ -1,654 +0,0 @@ -<!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 - let msg = '' - - // 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) - try { - 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) { - 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 - } - }), 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 - }), 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) - } - 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) - - // 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> diff --git a/index.html b/index.html deleted file mode 100644 index b334d18b47d1f8ab64e32028a15fac4c0b513d7e..0000000000000000000000000000000000000000 --- a/index.html +++ /dev/null @@ -1,704 +0,0 @@ -<!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="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 second - </label> - </div> - <div id='output'>Status: idle</div> - - <!-- Pseudo section --> - <select id='surveySelect' class='custom-select'> - </select> - <input id="cmin" type="number" value=""/> - <input id="cmax" type="number" value=""/> - <button id="capplyBtn" type="button" class="btn btn-info">Apply</button> - <div id="gd"></div> - <div id="hoverinfo" style="margin-left:80px;"></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="getRsBtn" type="button" class="btn btn-info">Get 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 settings --> - <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 settings</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 elecSpacing = 1 // 1 m - - // useful functions - 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) - } - - // run button - function runBtnFunc() { - sendCommand('{"cmd": "run_multiple_sequences"}', function(x) { - console.log(x['ohmpi_status']) - if (x['ohmpi_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['ohmpi_status'] - clearInterval(interv) - getData() - }) - } - let stopBtn = document.getElementById('stopBtn') - stopBtn.addEventListener('click', stopBtnFunc) - - // set configuration - function saveSettingsBtnFunc() { - // collect values from modal - let formVals = {} - formVals['nb_electrodes'] = parseInt(document.getElementById('nbElectrodes').value) - formVals['injection_duration'] = parseFloat(document.getElementById('injectionDuration').value) - formVals['nb_meas'] = parseInt(document.getElementById('nbMeasurements').value) - formVals['sequence_delay'] = parseInt(document.getElementById('sequenceDelay').value) - formVals['nb_stack'] = parseInt(document.getElementById('nbStack').value) - formVals['elec_spacing'] = parseFloat(document.getElementById('elecSpacing').value) - console.log(formVals) - elecSpacing = formVals['elec_spacing'] - - // define callback to send settigs to Pi - function settingsCallback() { - sendCommand(JSON.stringify({ - 'cmd': 'update_settings', - 'config': formVals - }), 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) - settingsCallback() - }, false) - } else { - console.log('no sequence uploaded') - formVals['sequence'] = '' - settingsCallback() - } - - - } - let saveConfigBtn = document.getElementById('saveConfigBtn') - saveConfigBtn.addEventListener('click', saveSettingsBtnFunc) - - // make pseudo plot - var trace = {} - let layout = {} - let tdata = [] - let layout2 = {} - let rsdata = [] - let rslayout = {} - - // initialize all plots - function initPlots() { - trace = { - x: [], - y: [], - mode: 'markers', - marker: { - size: 40, - color: [], - colorbar: { - title: 'App. res. [Ohm.m]', - cmin: 0, - cmax: 100, - } - } - } - layout = { - title: 'Pseudo-section', - yaxis: { - title: 'Pseudo-depth', - autorange: 'reversed' - }, - xaxis: { - title: 'X' - } - - } - Plotly.newPlot('gd', [trace], layout) - - // make time-serie plot - tdata = [] - layout2 = { - title: 'Time-serie', - yaxis: { - title: 'App. res. [Ohm.m]' - }, - xaxis: { - title: 'Sampling time' - } - } - Plotly.newPlot('ts', tdata, layout2) - - // bar chart for contact resistance - rsdata = [] - rslayout = { - title: 'Contact resistances', - yaxis: { - title: 'Resistance [kOhm]' - }, - xaxis: { - title: 'Consecutive electrodes' - } - } - Plotly.newPlot('rs', rsdata, rslayout) - } - initPlots() - - // hover function - var hoverInfo = document.getElementById('hoverinfo') - document.getElementById('gd').on('plotly_hover', function(data){ - var infotext = data.points.map(function(d){ - return (Math.round(d.data.marker.color[d.pointIndex], 2) + ' Ohm.m'); - }); - hoverInfo.innerHTML = infotext.join('<br/>'); - }) - .on('plotly_unhover', function(data){ - hoverInfo.innerHTML = ''; - }); - - // 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'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 < df['a'].length; i++) { - let a = df['a'][i] - let b = df['b'][i] - let m = df['m'][i] - let n = df['n'][i] - - // compute geometric factor assuming flat 2D surface - let am = Math.abs(a - m)*elecSpacing - let bm = Math.abs(b - m)*elecSpacing - let an = Math.abs(a - n)*elecSpacing - let bn = Math.abs(b - n)*elecSpacing - let K = 2*Math.PI/((1/am)-(1/bm)-(1/an)+(1/bn)) - app.push(df['rho'][i]*K) - //console.log(K) // same as resipy for the wenner case - - // computing pseudo-depth assuming 2D flat array - // let's sort the electrodes AB are the two left, MN, the two right - let abmn = [a, b, m, n] - abmn = abmn.sort((a, b) => a - b) - let ab = (abmn[0] + abmn[1])/2 - let mn = (abmn[2] + abmn[3])/2 - let dist = Math.abs(ab - mn) - xpos.push((Math.min(ab, mn) + dist/2)*elecSpacing) - ypos.push((Math.sqrt(2)/2*dist)*elecSpacing) - - /* - lookupDict = dict(zip(self.elec['label'], np.arange(self.elec.shape[0]))) - array = self.df[['a','b','m','n']].replace(lookupDict).values.astype(int) - elecm = self.elec[['x','y','z']].values.astype(float).copy() # electrode matrix - should be array of floats so np.inf work properly - - ### first determine if measurements are nested ### - #find mid points of AB - AB = (elecm[array[:,0]] + elecm[array[:,1]]) / 2 # mid points of AB - MN = (elecm[array[:,2]] + elecm[array[:,3]]) / 2 # mid points of MN - ABrad = np.sqrt(np.sum((elecm[array[:,0]] - AB)**2,axis=1)) # radius of AB circle - MNrad = np.sqrt(np.sum((elecm[array[:,2]] - MN)**2,axis=1)) # radius of MN circle - - Amn = np.sqrt(np.sum((elecm[array[:,0]] - MN)**2,axis=1)) # distance of A to mid point of MN - Bmn = np.sqrt(np.sum((elecm[array[:,1]] - MN)**2,axis=1)) # distance of B to mid point of MN - Nab = np.sqrt(np.sum((elecm[array[:,2]] - AB)**2,axis=1)) # distance of N to mid point of AB - Mab = np.sqrt(np.sum((elecm[array[:,3]] - AB)**2,axis=1)) # distance of M to mid point of AB - - iABinMN = (Amn < MNrad) & (Bmn < MNrad) - iMNinAB = (Nab < ABrad) & (Mab < ABrad) - inested = iABinMN | iMNinAB #if AB encompasses MN or MN encompasses AB - - # so it will never be taken as minimium - elecm[self.elec['remote'].values,:] = np.inf - - # compute midpoint position of AB and MN dipoles - elecx = elecm[:,0] - elecy = elecm[:,1] - - #CURRENT ELECTRODE MIDPOINTS - caddx = np.abs(elecx[array[:,0]]-elecx[array[:,1]])/2 - caddy = np.abs(elecy[array[:,0]]-elecy[array[:,1]])/2 - caddx[np.isinf(caddx)] = 0 - caddy[np.isinf(caddy)] = 0 - cmiddlex = np.min([elecx[array[:,0]], elecx[array[:,1]]], axis=0) + caddx - cmiddley = np.min([elecy[array[:,0]], elecy[array[:,1]]], axis=0) + caddy - - #POTENTIAL ELECTRODE MIDPOINTS - paddx = np.abs(elecx[array[:,2]]-elecx[array[:,3]])/2 - paddy = np.abs(elecy[array[:,2]]-elecy[array[:,3]])/2 - paddx[np.isinf(paddx)] = 0 - paddy[np.isinf(paddy)] = 0 - pmiddlex = np.min([elecx[array[:,2]], elecx[array[:,3]]], axis=0) + paddx - pmiddley = np.min([elecy[array[:,2]], elecy[array[:,3]]], axis=0) + paddy - - - # for non-nested measurements - xposNonNested = np.min([cmiddlex, pmiddlex], axis=0) + np.abs(cmiddlex-pmiddlex)/2 - yposNonNested = np.min([cmiddley, pmiddley], axis=0) + np.abs(cmiddley-pmiddley)/2 - pcdist = np.sqrt((cmiddlex-pmiddlex)**2 + (cmiddley-pmiddley)**2) - - # zposNonNested = np.sqrt(2)/2*pcdist - zposNonNested = pcdist/4 - - if np.all(cmiddley-pmiddley == 0): - zposNonNested = 0.25*pcdist - else: # for 3D arrays where there are mid-line measurements, this works closer to inversion results - zposNonNested = np.sqrt(2)/2*pcdist - - # for nested measurements use formula of Dalhin 2006 - xposNested = np.zeros(len(pmiddlex)) - yposNested = np.zeros(len(pmiddlex)) - outerElec1 = np.zeros((len(pmiddlex), 2)) # position of one electrode of outer dipole - outerElec2 = np.zeros((len(pmiddlex), 2)) # position of one electrode of outer dipole - # innerMid = np.zeros((len(pmiddlex), 2)) # middle of inner dipole - if np.sum(iMNinAB) > 0: - xposNested[iMNinAB] = pmiddlex[iMNinAB] - yposNested[iMNinAB] = pmiddley[iMNinAB] - outerElec1[iMNinAB] = np.c_[elecx[array[iMNinAB,0]], elecy[array[iMNinAB,0]]] - outerElec2[iMNinAB] = np.c_[elecx[array[iMNinAB,1]], elecy[array[iMNinAB,1]]] - - if np.sum(iABinMN) > 0: - xposNested[iABinMN] = cmiddlex[iABinMN] - yposNested[iABinMN] = cmiddley[iABinMN] - outerElec1[iABinMN] = np.c_[elecx[array[iABinMN,2]], elecy[array[iABinMN,2]]] - outerElec2[iABinMN] = np.c_[elecx[array[iABinMN,3]], elecy[array[iABinMN,3]]] - - innerMid = np.c_[pmiddlex, pmiddley] # always use potential dipole - - apdist = np.sqrt(np.sum((outerElec1-innerMid)**2, axis=1)) - bpdist = np.sqrt(np.sum((outerElec2-innerMid)**2, axis=1)) - zposNested = np.min([apdist, bpdist], axis=0)/3 - - xpos = np.zeros_like(pmiddlex) - ypos = np.zeros_like(pmiddlex) - zpos = np.zeros_like(pmiddlex) - - xpos[~inested] = xposNonNested[~inested] - xpos[inested] = xposNested[inested] - - ypos[~inested] = yposNonNested[~inested] - ypos[inested] = yposNested[inested] - - zpos[~inested] = zposNonNested[~inested] - zpos[inested] = zposNested[inested] - - */ - } - //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') - - // run RS check - function rsBtnFunc() { - sendCommand('{"cmd": "rsCheck"}', function (a) {}) - } - let rsBtn = document.getElementById('rsBtn') - rsBtn.addEventListener('click', rsBtnFunc) - - // get RS check data - function getRsBtnFunc() { - sendCommand('{"cmd": "getRsCheck"}', function(res) { - // update the bar plot - rsdata.push({ - x: res['data']['AB'], - y: res['data']['res'], - name: 'RS', - type: 'bar' - }) - Plotly.redraw('rs') - }) - } - let getRsBtn = document.getElementById('getRsBtn') - getRsBtn.addEventListener('click', getRsBtnFunc) - - // clear RS graph - function rsClearBtnFunc() { - rsdata = [] - Plotly.newPlot('rs', rsdata, rslayout) - } - let rsClearBtn = document.getElementById('rsClearBtn') - rsClearBtn.addEventListener('click', rsClearBtnFunc) - - // getData - function getData() { - let surveyNames = [] - sendCommand(JSON.stringify({ - 'cmd': 'getData', - 'surveyNames': surveyNames - // 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 - 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 - let idiff = false - if (data[surveyNames[0]] != undefined) { - idiff = quads.length != data[surveyNames[0]]['a'].length - } - //console.log('idiff=', idiff, quads.length, data[surveyNames[0]]['a'].length) - if (((quads.length == 0) | idiff) & (data[surveyNames[0]] != undefined)){ - console.log('updating list of quadrupoles') - quads = [] - 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) - - // 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['ohmpi_status'] + ' (all data cleared)' - console.log('all data removed') - initPlots() // reset all plots - }) - } - 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> diff --git a/ohmpi/__init__.py b/ohmpi/__init__.py index 09c495ea868bac310c7f76790aca8a1b691d6de6..d1c1cf1ce4567059e2106febb97432bcdad4cfa1 100644 --- a/ohmpi/__init__.py +++ b/ohmpi/__init__.py @@ -1 +1,2 @@ # from .ohmpi import OhmPi +#from ohmpi.ohmpi import OhmPi diff --git a/ohmpi/hardware_components/pwr_batt.py b/ohmpi/hardware_components/pwr_batt.py index 650b5dcde3e86f10d7866aa53049032051ce0ee7..dc67c8b88a742df803691446e0474d5f9c557e83 100644 --- a/ohmpi/hardware_components/pwr_batt.py +++ b/ohmpi/hardware_components/pwr_batt.py @@ -8,7 +8,8 @@ from ohmpi.utils import enforce_specs SPECS = {'model': {'default': os.path.basename(__file__).rstrip('.py')}, 'voltage': {'default': 12., 'max': 12., 'min': 12.}, 'current_adjustable': {'default': False}, - 'voltage_adjustable': {'default': False} + 'voltage_adjustable': {'default': False}, + 'interface': {'default': 'none'}, } diff --git a/ohmpi/hardware_components/raspberry_pi.py b/ohmpi/hardware_components/raspberry_pi.py index f2a59642824a9869d1cb4e7b23f437e278e2a86a..d45ad095c3cf7f7ef977d8a67677378f9d38521b 100644 --- a/ohmpi/hardware_components/raspberry_pi.py +++ b/ohmpi/hardware_components/raspberry_pi.py @@ -34,6 +34,9 @@ class Ctl(CtlAbstract): super().__init__(**kwargs) self.interfaces = dict() + # None interface for battery + self.interfaces['none'] = None + # warnings.filterwarnings("error") # to filter out adafruit warning about setting I2C frequency # I2C try: diff --git a/ohmpi/hardware_system.py b/ohmpi/hardware_system.py index aa2161f9c2d6356d4433b20edee3213982185e0a..d1efa5779a8200d4623489d7ad504e2f67052771 100644 --- a/ohmpi/hardware_system.py +++ b/ohmpi/hardware_system.py @@ -104,11 +104,11 @@ class OhmPiHardware: if isinstance(ctl_mod, str): ctl_mod = importlib.import_module(f'ohmpi.hardware_components.{ctl_mod}') HARDWARE_CONFIG['pwr']['ctl'] = ctl_mod.Ctl(**HARDWARE_CONFIG['pwr']['ctl']) - HARDWARE_CONFIG['pwr'].update({'connection': - HARDWARE_CONFIG['pwr'].pop('connection', - HARDWARE_CONFIG['pwr']['ctl'].interfaces[ - HARDWARE_CONFIG['pwr'].pop( - 'interface_name', None)])}) + #if 'interface_name' in HARDWARE_CONFIG['pwr']: + HARDWARE_CONFIG['pwr'].update({ + 'connection': HARDWARE_CONFIG['pwr'].pop( + 'connection', HARDWARE_CONFIG['pwr']['ctl'].interfaces[ + HARDWARE_CONFIG['pwr'].pop('interface_name', None)])}) HARDWARE_CONFIG['pwr'].update({'exec_logger': self.exec_logger, 'data_logger': self.data_logger, 'soh_logger': self.soh_logger}) @@ -167,6 +167,22 @@ class OhmPiHardware: self._start_time = None # time of the beginning of a readings acquisition self._pulse = 0 # pulse number self.exec_logger.event(f'OhmPiHardware\tinit\tend\t{datetime.datetime.utcnow()}') + self._pwr_state = 'off' + + @property + def pwr_state(self): + return self._pwr_state + + @pwr_state.setter + def pwr_state(self, state): + if state == 'on': + self.tx.pwr_state = 'on' + self._pwr_state = 'on' + self.exec_logger.debug(f'{self.model} cannot be switched on') + elif state == 'off': + self.tx.pwr_state = 'off' + self._pwr_state = 'off' + self.exec_logger.debug(f'{self.model} cannot be switched off') def _clear_values(self): self.readings = np.array([]) @@ -420,8 +436,8 @@ class OhmPiHardware: append=False): self.exec_logger.event(f'OhmPiHardware\tvab_square_wave\tbegin\t{datetime.datetime.utcnow()}') switch_pwr_off, switch_tx_pwr_off = False, False - if self.tx.pwr_state == 'off': - self.tx.pwr_state = 'on' + if self.pwr_state == 'off': + self.pwr_state = 'on' switch_tx_pwr_off = True # if self.tx.pwr.pwr_state == 'off': # self.tx.pwr.pwr_state = 'on' @@ -442,7 +458,7 @@ class OhmPiHardware: if switch_pwr_off: self.tx.pwr.pwr_state = 'off' if switch_tx_pwr_off: - self.tx.pwr_state = 'off' + self.pwr_state = 'off' def _vab_pulse(self, vab=None, duration=1., sampling_rate=None, polarity=1, append=False): """ Gets VMN and IAB from a single voltage pulse @@ -466,8 +482,8 @@ class OhmPiHardware: def _vab_pulses(self, vab, durations, sampling_rate, polarities=None, append=False): switch_pwr_off, switch_tx_pwr_off = False, False - if self.tx.pwr_state == 'off': - self.tx.pwr_state = 'on' + if self.pwr_state == 'off': + self.pwr_state = 'on' switch_pwr_off = True n_pulses = len(durations) self.exec_logger.debug(f'n_pulses: {n_pulses}') @@ -492,7 +508,8 @@ class OhmPiHardware: if switch_pwr_off: self.tx.pwr.pwr_state = 'off' if switch_tx_pwr_off: - self.tx.pwr_state = 'off' + self.pwr_state = 'off' + def switch_mux(self, electrodes, roles=None, state='off', **kwargs): """Switches on multiplexer relays for given quadrupole. diff --git a/ohmpi/ohmpi.py b/ohmpi/ohmpi.py index cc260ae63f23341ae43831edcb5152ff5c6a4b45..2b49830e20d7af8611fa47c5810fa21f7ecf8891 100644 --- a/ohmpi/ohmpi.py +++ b/ohmpi/ohmpi.py @@ -451,6 +451,12 @@ class OhmPi(object): cmd_id : str, optional Unique command identifier """ + # check pwr is on, if not, let's turn it on + switch_power_off = False + if self._hw.pwr_state == 'off': + self._hw.pwr_state = 'on' + switch_power_off = True + self.exec_logger.debug('Starting measurement') self.exec_logger.debug('Waiting for data') @@ -532,6 +538,11 @@ class OhmPi(object): else: self.exec_logger.info(f'Skipping {quad}') self.switch_mux_off(quad, cmd_id) + + # if power was off before measurement, let's turn if off + if switch_power_off: + self._hw.pwr_state = 'off' + return d def run_multiple_sequences(self, cmd_id=None, sequence_delay=None, nb_meas=None, **kwargs): # NOTE : could be renamed repeat_sequence @@ -588,6 +599,8 @@ class OhmPi(object): cmd_id : str, optional Unique command identifier """ + # switch power on + self._hw.pwr_state = 'on' self.status = 'running' self.exec_logger.debug(f'Status: {self.status}') self.exec_logger.debug(f'Measuring sequence: {self.sequence}') @@ -669,7 +682,7 @@ class OhmPi(object): self.append_and_save(filename, acquired_data) self.exec_logger.debug(f'quadrupole {i + 1:d}/{n:d}') - # self.switch_dps('off') + self._hw.pwr_state = 'off' self.status = 'idle' def run_sequence_async(self, cmd_id=None, **kwargs): @@ -810,7 +823,7 @@ class OhmPi(object): self.exec_logger.debug(f'tx pwr voltage: {self._hw.tx.pwr.voltage}, rx max voltage: {self._hw.rx._voltage_max}') return False else: - if np.array(quadrupole).all() == np.array([0, 0, 0, 0]).all(): # NOTE: No mux + if quadrupole == [0, 0, 0, 0]: # NOTE: No mux return True else: return self._hw.switch_mux(electrodes=quadrupole, state='on', bypass_check=bypass_check)