diff --git a/configs/config_mb_2024_0_2__4_mux_2024_dps5005.py b/configs/config_mb_2024_0_2__4_mux_2024_dps5005.py
index 31c2538e8a5923f963f0f0fbce254307a2be17a4..d36472af02d63b92c01ea4a0854063d617810d42 100644
--- a/configs/config_mb_2024_0_2__4_mux_2024_dps5005.py
+++ b/configs/config_mb_2024_0_2__4_mux_2024_dps5005.py
@@ -21,9 +21,10 @@ HARDWARE_CONFIG = {
     'pwr': {'model': 'pwr_dps5005', 'voltage': 3., 'interface_name': 'modbus'},
     'tx':  {'model': 'mb_2024_0_2',
              'voltage_max': 50.,  # Maximum voltage supported by the TX board [V]
-             'current_max': 4.8/(50*r_shunt),  # Maximum voltage read by the current ADC on the TX board [A]
+             'current_max': 4.8/(50*r_shunt)/5,  # Maximum voltage read by the current ADC on the TX board [A]
              'r_shunt': r_shunt,  # Shunt resistance in Ohms
-             'interface_name': 'i2c'
+             'interface_name': 'i2c',
+             'vmn_hardware_offset': 2501.
             },
     'rx':  {'model': 'mb_2024_0_2',
              'coef_p2': 1.00,  # slope for conversion for ADS, measurement in V/V
diff --git a/dev/test_mb_2023_4_mux_2023.py b/dev/test_mb_2023_4_mux_2023.py
index 69b292e43b3fc8be7c1d96a9d83dc52c6b5be31e..ca9c9c669028d077929a075488e37c40f928fb04 100644
--- a/dev/test_mb_2023_4_mux_2023.py
+++ b/dev/test_mb_2023_4_mux_2023.py
@@ -84,10 +84,11 @@ if within_ohmpi:
 
     print('Starting test with OhmPi.')
     k = OhmPi()
-    k.get_data()
-    # k.load_sequence(os.path.join(os.path.dirname(__file__), '../sequences/test_circuit_1423.txt'))
-    # k.reset_mux()
-    # k.run_sequence(injection_duration=0.2)
+    # k.get_data()
+    k.load_sequence(os.path.join(os.path.dirname(__file__), '../sequences/wenner.txt'))
+    k.reset_mux()
+    # k.run_multiple_sequences(sequence_delay=20, nb_meas=3)
+    k.run_sequence(injection_duration=0.2)
     # k.rs_check(tx_volt=4)
     # k.test_mux(mux_id=None, activation_time=0.2)
     # k._hw.switch_mux([A, B, M, N], state='on')
diff --git a/dev/test_mb_2024_4_mux_2024.py b/dev/test_mb_2024_4_mux_2024.py
index 7d3142d8b4941ab6bef18abe239bb017747fd8ee..d30d4daec881c5fd5640855ec3b948552c6e9260 100644
--- a/dev/test_mb_2024_4_mux_2024.py
+++ b/dev/test_mb_2024_4_mux_2024.py
@@ -7,9 +7,9 @@ import time
 import logging
 from ohmpi.config import HARDWARE_CONFIG
 
-stand_alone = True
+stand_alone = False
 part_of_hardware_system = False
-within_ohmpi = False
+within_ohmpi = True
 
 # Stand alone
 if stand_alone:
@@ -109,14 +109,15 @@ if within_ohmpi:
     # 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()
-    # k.load_sequence('sequences/9991_GRAD_16_s1_a1.txt')
-    # k.run_sequence(tx_volt=5, injection_duration=1., nb_stack=2, duty_cycle=0.5)
+    k.load_sequence('sequences/9991_GRAD_16_s1_a1.txt')
+    k.run_sequence(tx_volt=5, injection_duration=1., nb_stack=2, duty_cycle=0.5)
     print('using OhmPi')
-    d = k.run_measurement([A, B, M, N], injection_duration=1., nb_stack=2, duty_cycle=0.5, tx_volt=5.)
+    # d = k.run_measurement([A, B, M, N], injection_duration=1., nb_stack=2, duty_cycle=0.5, tx_volt=5., delay=0.01)
     # 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()
+    # 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.html
similarity index 84%
rename from index-mqtt.html
rename to index.html
index 69aa13d99bd8a22468805e6ef53fdb85b46ab972..2d8c4eb18c74556192c743c7c11acc60a58d8f20 100755
--- a/index-mqtt.html
+++ b/index.html
@@ -1,5 +1,8 @@
 <!DOCTYPE html>
-<!-- debugging instruction to see the messages passing on the RPi
+<!-- 
+    To access the interface,
+    1. connect to the 'raspi-webgui' wifi with the given password
+    Open a browserdebugging instruction to see the messages passing on the RPi
 mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
 -->
 <html>
@@ -17,9 +20,10 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
     <!-- <script src="js/danfojs/bundle.min.js"></script> -->
     <!-- <script src="js/mqtt.min.js"></script> -->
     <script src="js/paho/paho-mqtt.js"></script>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
 </head>
 <body>
-    <div class='container'>
+    <div class='container-fluid'>
         <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>
@@ -39,8 +43,8 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         <!-- Pseudo section -->
         <select id='surveySelect' class='custom-select'>
         </select>
-        <input id="cmin" type="number" value="0"/>
-        <input id="cmax" type="number" value="150"/>
+        <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>
         
@@ -64,8 +68,8 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         <select id="surveySelectInv" class='custom-select'>
         </select>
         <button id="invertBtn" type="button" class="btn btn-info">Run inversion</button>
-        <input id="cminInv" type="number" value="0"/>
-        <input id="cmaxInv" type="number" value="150"/>
+        <input id="cminInv" type="number" value=""/>
+        <input id="cmaxInv" type="number" value=""/>
         <button id="capplyBtnInv" type="button" class="btn btn-info">Apply</button>
         <div id="inv"></div>
         
@@ -89,33 +93,39 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 <div class="modal-body">
                     <form>
                         <div class="form-group row">
-                          <label for="nbElectrodes" class="col-sm-2 col-form-label">Nb electrodes</label>
+                          <label for="nb_electrodes" 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">
+                            <input type="number" class="form-control-number" id="nb_electrodes" value="64">
                           </div>
                         </div>
                         <div class="form-group row">
-                            <label for="injectionDuration" class="col-sm-2 col-form-label">Injection duration [s]</label>
+                            <label for="injection_duration" 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">
+                              <input type="number" class="form-control-number" id="injection_duration" value="0.2">
                             </div>
                           </div>
                           <div class="form-group row">
-                            <label for="nbMeasurements" class="col-sm-2 col-form-label">Nb Measurements</label>
+                            <label for="nb_meas" 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">
+                              <input type="number" class="form-control-number" id="nb_meas" value="1">
                             </div>
                           </div>
                           <div class="form-group row">
-                            <label for="sequenceDelay" class="col-sm-2 col-form-label">Sequence delay [s]</label>
+                            <label for="sequence_delay" 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">
+                              <input type="number" class="form-control-number" id="sequence_delay" value="100">
                             </div>
                           </div>
                           <div class="form-group row">
-                            <label for="nbStack" class="col-sm-2 col-form-label">Nb stack</label>
+                            <label for="nb_stack" 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">
+                              <input type="number" class="form-control-number" id="nb_stack" value="1">
+                            </div>
+                          </div>
+                          <div class="form-group row">
+                            <label for="duty_cycle" class="col-sm-2 col-form-label">Duty cycle [0 -> 1]</label>
+                            <div class="col-sm-10">
+                              <input type="number" class="form-control-number" id="duty_cycle" value="0.5">
                             </div>
                           </div>
                           <div class="form-group row">
@@ -202,17 +212,32 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         }
         
         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)
+                    console.log('DATA LOG:', payload)
                     let ddic = JSON.parse(payload.split('INFO:')[1])
 
-                    // check cmd_id is any
-                    processMessage(ddic)
+                    // RS check data
+                    if ('rsdata' in ddic) {
+                        rsdata[0]['x'].push(ddic['rsdata']['A']), //, + '-' + ddic['rsdata']['B'],
+                        rsdata[0]['y'].push(ddic['rsdata']['rs']),
+                        Plotly.redraw('rs')
+                    
+                    } else if ('download' in ddic) {
+                        let dwl = document.getElementById('download')
+                        dwl.setAttribute('href', serverUrl + '/data.zip')
+                        dwl.setAttribute('download', 'data.zip')
+                        dwl.click()
+                    } else { // data or results from inversion
+                        processMessage(ddic)
+                    }
+
+                    if ('status' in  ddic) {
+                        document.getElementById('output').value = ddic['status']
+                    }
 
                     // usually these don't have a cmd_id so we are not sure when
 
@@ -303,10 +328,13 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         // 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
+            let formVals = {
+                'nb_electrodes': parseInt(document.getElementById('nb_electrodes').value),
+                'injection_duration': parseFloat(document.getElementById('injection_duration').value),
+                'nb_meas': parseInt(document.getElementById('nb_meas').value),
+                'sequence_delay': parseFloat(document.getElementById('sequence_delay').value),
+                'nb_stack': parseInt(document.getElementById('nb_stack').value),   
+                'duty_cycle': parseFloat(document.getElementById('duty_cycle').value),
             }
             console.log(formVals)
             
@@ -392,7 +420,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
             }
 
         }
-        Plotly.newPlot('gd', [trace], layout)
+        Plotly.newPlot('gd', [trace], layout, {responsive: true})
 
         // make time-serie plot
         let tdata = []
@@ -405,7 +433,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 title: 'Sampling time'
             }
         }
-        Plotly.newPlot('ts', tdata, layout2)
+        Plotly.newPlot('ts', tdata, layout2, {responsive: true})
 
         // add trace to time-serie plot
         function addTraceBtnFunc() {
@@ -417,7 +445,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 name: val,
                 type: 'scatter'
             })
-            Plotly.newPlot('ts', tdata, layout2)
+            Plotly.newPlot('ts', tdata, layout2, {responsive: true})
             getData()
         }
         let addTraceBtn = document.getElementById('addTraceBtn')
@@ -427,13 +455,14 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         function removeTracesBtnFunc() {
             squads = []
             tdata = []            
-            Plotly.newPlot('ts', tdata, layout2)
+            Plotly.newPlot('ts', tdata, layout2, {responsive: true})
         }
         let removeTracesBtn = document.getElementById('removeTracesBtn')
         removeTracesBtn.addEventListener('click', removeTracesBtnFunc)
 
         // callback function to draw the plot
         function surveySelectFunc(el) {
+            let elecSpacing = parseFloat(document.getElementById('elecSpacing').value)
             let surveyName = el['target'].value
             let df = data[surveyName]
             if (df != undefined) {
@@ -448,25 +477,44 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 let ypos = []
                 let app = []
                 for (let i = 0; i < a.length; i++) {
+                   
                     let emin = Math.min(...[a[i], b[i], m[i], n[i]])
                     let emax = Math.max(...[a[i], b[i], m[i], n[i]])
                     let dist = Math.abs(emax - emin)
                     xpos.push(emin + dist/2)
                     ypos.push(Math.sqrt(2)/2 * dist)
-                    // let ab = (a[i] + b[i])/2
-                    // let mn = (m[i] + n[i])/2
-                    // 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)
+                    
+                    // 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)
                 }
-                console.log('xpos', xpos)
-                console.log('ypos', ypos)
-                console.log(app)
+                console.log('========', app, xpos, ypos)
                 // update the trace and redraw the figure
                 trace['x'] = xpos
                 trace['y'] = ypos
@@ -479,7 +527,13 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         let surveySelect = document.getElementById('surveySelect')
 
         // bar chart for contact resistance
-        let rsdata = []
+        let rsdata = [{
+            'x': [],
+            'y': [],
+            name: 'RS',
+            type: 'bar',
+            }
+        ]
         let rslayout = {
             title: 'Contact resistances',
             yaxis: {
@@ -489,7 +543,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 title: 'Consecutive electrodes'
             }
         }
-        Plotly.newPlot('rs', rsdata, rslayout)
+        Plotly.newPlot('rs', rsdata, rslayout, {responsive: true})
         
         // run RS check
         function rsBtnFunc() {
@@ -509,8 +563,13 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         
         // clear RS graph
         function rsClearBtnFunc() {
-            rsdata = []
-            Plotly.newPlot('rs', rsdata, rslayout)
+            rsdata = [{
+            'x': [],
+            'y': [],
+            name: 'RS',
+            type: 'bar',
+            }]
+            Plotly.newPlot('rs', rsdata, rslayout, {responsive: true})
         }
         let rsClearBtn = document.getElementById('rsClearBtn')
         rsClearBtn.addEventListener('click', rsClearBtnFunc)
@@ -530,7 +589,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
             //if (('status' in ddic) | ('data' in ddic)) {
             if (ddic.constructor == Object) {  // it's a dictionnary
                 // acquisition related
-                processData(ddic)
+                processData(ddic)                
             } else {
                 // inversion related
                 invertedData = ddic
@@ -686,7 +745,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
                 }   
             }
             
-            Plotly.newPlot('inv', invData, invLayout)
+            Plotly.newPlot('inv', invData, invLayout, {responsive: true})
             var btn = document.getElementById('invertBtn')
             btn.innerText = 'Run inversion'
             btn.className = 'btn btn-info'
@@ -760,11 +819,8 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl
         
         // 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()
+            sendCommand('{"cmd": "download_data"}', function(x) {
+                console.log(x)
             })
         }
         let downloadBtn = document.getElementById('downloadBtn')
diff --git a/index_old.html b/index_old.html
new file mode 100644
index 0000000000000000000000000000000000000000..b334d18b47d1f8ab64e32028a15fac4c0b513d7e
--- /dev/null
+++ b/index_old.html
@@ -0,0 +1,704 @@
+<!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">&#9654</button>
+        <button id='stopBtn' type="button" class="btn btn-warning">&#9724</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">&times;</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/config.py b/ohmpi/config.py
index fa30ccaadc748d016cd2307bff9678217e04259d..40665972ec78fcbb165cf92d2b3b5fb2fe617836 100644
--- a/ohmpi/config.py
+++ b/ohmpi/config.py
@@ -17,14 +17,15 @@ OHMPI_CONFIG = {
     'settings': 'ohmpi_settings.json',  # INSERT YOUR FAVORITE SETTINGS FILE HERE
 }
 
+r_shunt = 2.
 HARDWARE_CONFIG = {
     'ctl': {'model': 'raspberry_pi'},
     '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]
-             'r_shunt': 2.,  # Shunt resistance in Ohms
-             'interface_name': 'i2c',
+             'voltage_max': 50.,  # Maximum voltage supported by the TX board [V]
+             'current_max': 4.80/(50*r_shunt),  # Maximum voltage read by the current ADC on the TX board [A]
+             'r_shunt': r_shunt,  # Shunt resistance in Ohms
+             'interface_name': 'i2c'
             },
     'rx':  {'model': 'mb_2023_0_X',
             'coef_p2': 2.50,  # slope for conversion for ADS, measurement in V/V
diff --git a/ohmpi/hardware_components/abstract_hardware_components.py b/ohmpi/hardware_components/abstract_hardware_components.py
index be1e51e2c2712e249988d975d014503f8b1eacc7..5b98a620f4f1873104c14a6a3c6f4cc84ef6e7cb 100644
--- a/ohmpi/hardware_components/abstract_hardware_components.py
+++ b/ohmpi/hardware_components/abstract_hardware_components.py
@@ -423,7 +423,7 @@ class TxAbstract(ABC):
     def pwr_state(self, state):
         if state == 'on':
             self._pwr_state = 'on'
-            if not self.pwr.switchable:
+            if not self.pwr._switchable:
                 self.exec_logger.debug(f'{self.model} cannot switch on power source')
             self.pwr.reload_settings()
         elif state == 'off':
diff --git a/ohmpi/hardware_components/mb_2023_0_X.py b/ohmpi/hardware_components/mb_2023_0_X.py
index f9ae9368f2ec997d3fdf598b97a4644993e061de..7ba2a1652aa1d34523076bb589523293f70863d1 100644
--- a/ohmpi/hardware_components/mb_2023_0_X.py
+++ b/ohmpi/hardware_components/mb_2023_0_X.py
@@ -21,6 +21,7 @@ SPECS = {'rx': {'model': {'default': os.path.basename(__file__).rstrip('.py')},
                 'mcp_address': {'default': None},
                 'ads_address': {'default': 0x49},
                 'voltage_min': {'default': 10.0},
+                'voltage_max': {'default': 5000.0},  # [mV]
                 'vmn_hardware_offset': {'default': 0.}
                 },
          'tx': {'model': {'default': os.path.basename(__file__).rstrip('.py')},
diff --git a/ohmpi/hardware_components/mb_2024_0_2.py b/ohmpi/hardware_components/mb_2024_0_2.py
index 33fe7ee461234b44c7a711558b96b85f5dc0742f..99f3883315f9116f9c26e67f8c7ad99e49b8afb9 100644
--- a/ohmpi/hardware_components/mb_2024_0_2.py
+++ b/ohmpi/hardware_components/mb_2024_0_2.py
@@ -21,6 +21,7 @@ SPECS = {'rx': {'model': {'default': os.path.basename(__file__).rstrip('.py')},
                 'mcp_address': {'default': 0x27},
                 'ads_address': {'default': 0x49},
                 'voltage_min': {'default': 10.0},
+                'voltage_max': {'default': 5000.0},  # [mV]
                 'dg411_gain_ratio': {'default': 1/2},  # lowest resistor value over sum of resistor values
                 'vmn_hardware_offset': {'default': 2500.},
                 },
diff --git a/ohmpi/hardware_components/pwr_dps5005.py b/ohmpi/hardware_components/pwr_dps5005.py
index 13fed777042690c38fc74104e46bbbd54028014f..f6279ed22ca678fdc4fedd0a3f69504610a1b2ad 100644
--- a/ohmpi/hardware_components/pwr_dps5005.py
+++ b/ohmpi/hardware_components/pwr_dps5005.py
@@ -71,8 +71,8 @@ class Pwr(PwrAbstract):
         self._battery_voltage = self.connection.read_register(0x05, 2)
         return self._battery_voltage
 
-    def current_max(self, value):
-        self.connection.write_register(0x0001, value * 10, 0)
+    def current_max(self, value):  # [mA]
+        self.connection.write_register(0x0001, int(value*1000), 0)
 
     @property
     def pwr_state(self):
@@ -89,6 +89,8 @@ class Pwr(PwrAbstract):
             """
         if state == 'on':
             self.connection.write_register(0x09, 1)
+            self.current_max(self._current_max)
+            print(self._current_max)
             self._pwr_state = 'on'
             self.exec_logger.debug(f'{self.model} is on')
             time.sleep(self._pwr_latency) # from pwr specs
diff --git a/ohmpi/hardware_system.py b/ohmpi/hardware_system.py
index 19645f09fb90927234a39a0dfcaa2ba11604c587..e91e58e3b89a88486864e9c072b2f3d114eff5ff 100644
--- a/ohmpi/hardware_system.py
+++ b/ohmpi/hardware_system.py
@@ -43,12 +43,14 @@ for k, v in rx_module.SPECS['rx'].items():
     except Exception as e:
         print(f'Cannot set value {v} in RX_CONFIG[{k}]:\n{e}')
 
-current_max = np.min([TX_CONFIG['current_max'],  # TODO: replace 50 by a TX config
+current_max = np.min([TX_CONFIG['current_max'],  HARDWARE_CONFIG['pwr'].pop('current_max', np.inf), # TODO: replace 50 by a TX config
                       np.min(np.hstack((np.inf, [MUX_CONFIG[i].pop('current_max', np.inf) for i in MUX_CONFIG.keys()])))])
 voltage_max = np.min([TX_CONFIG['voltage_max'],
                       np.min(np.hstack((np.inf, [MUX_CONFIG[i].pop('voltage_max', np.inf) for i in MUX_CONFIG.keys()])))])
 voltage_min = RX_CONFIG['voltage_min']
 
+print(f'Current max: {current_max:.2f}')
+
 
 def elapsed_seconds(start_time):
     lap = datetime.datetime.utcnow() - start_time
@@ -99,6 +101,7 @@ class OhmPiHardware:
         HARDWARE_CONFIG['pwr'].pop('model')
         HARDWARE_CONFIG['pwr'].update(**HARDWARE_CONFIG['pwr'])  # NOTE: Explain why this is needed or delete me
         HARDWARE_CONFIG['pwr'].update({'ctl': HARDWARE_CONFIG['pwr'].pop('ctl', self.ctl)})
+        HARDWARE_CONFIG['pwr'].update({'current_max': current_max})
         if isinstance(HARDWARE_CONFIG['pwr']['ctl'], dict):
             ctl_mod = HARDWARE_CONFIG['pwr']['ctl'].pop('model', self.ctl)
             if isinstance(ctl_mod, str):
@@ -135,6 +138,7 @@ class OhmPiHardware:
         if isinstance(self.tx, dict):
             self.tx = tx_module.Tx(**self.tx)
         self.tx.pwr = self.pwr
+        self.tx.pwr._current_max = current_max
 
         # Initialize Muxes
         self._cabling = kwargs.pop('cabling', {})
@@ -355,7 +359,8 @@ class OhmPiHardware:
         return new_vab
 
     def _compute_tx_volt(self, pulse_duration=0.1, strategy='vmax', tx_volt=5., vab_max=voltage_max,
-                         iab_max=current_max, vmn_max=5., vmn_min=voltage_min, polarities=(1, -1), delay=0.050):
+                         iab_max=current_max, vmn_max=None, vmn_min=voltage_min, polarities=(1, -1), delay=0.05,
+                         p_max=None, diff_vab_lim=2.5, n_steps=4):
         # TODO: Optimise how to pass iab_max, vab_max, vmn_min
         """Estimates best Tx voltage based on different strategies.
         At first a half-cycle is made for a short duration with a fixed
@@ -397,12 +402,11 @@ class OhmPiHardware:
         """
 
         if self.tx.pwr.voltage_adjustable:
-            # Get those values from components
-            p_max = vab_max * iab_max
-
-            # define a sill
-            diff_vab_lim = 2.5
-            n_steps = 4
+            if vmn_max is None:
+                vmn_max = self.rx._voltage_max / 1000.
+            print(f'Vmn max: {vmn_max}')
+            if p_max is None:
+                p_max = vab_max * iab_max
 
             # Set gain at min
             self.rx.reset_gain()
@@ -449,9 +453,9 @@ class OhmPiHardware:
                 if self.tx.pwr.voltage_adjustable:
                     self.tx.voltage = vab_list[k]
             vab_opt = vab_list[k]
-            print(f'Selected Vab: {vab_opt:.2f}')
-            if switch_pwr_off:
-                self.tx.pwr.pwr_state = 'off'
+            # print(f'Selected Vab: {vab_opt:.2f}')
+            # if switch_pwr_off:
+            #     self.tx.pwr.pwr_state = 'off'
         else:
             vab_opt = tx_volt
         # if strategy == 'vmax':
diff --git a/ohmpi/http_interface.py b/ohmpi/http_interface.py
index 7fd04e37d9e8ae509f56a8e85ea043bd6b22ac73..2b7e6a6534495a6757ba03890fa4220f512cde8a 100644
--- a/ohmpi/http_interface.py
+++ b/ohmpi/http_interface.py
@@ -2,77 +2,74 @@ from http.server import SimpleHTTPRequestHandler, HTTPServer
 import os
 import json
 import uuid
-from config import MQTT_CONTROL_CONFIG, OHMPI_CONFIG
-from termcolor import colored
-import pandas as pd
+# from config import MQTT_CONTROL_CONFIG, OHMPI_CONFIG
+# from termcolor import colored
+# import pandas as pd
 import shutil
 import time
-import numpy as np
+# import numpy as np
 from io import StringIO
 import threading
-import paho.mqtt.client as mqtt_client
-import paho.mqtt.publish as publish
+# import paho.mqtt.client as mqtt_client
+# import paho.mqtt.publish as publish
 
 hostName = "0.0.0.0"  # for AP mode (not AP-STA)
-serverPort = 8080
+serverPort = 8000
 
 # https://gist.github.com/MichaelCurrie/19394abc19abd0de4473b595c0e37a3a
 
-ctrl_broker = MQTT_CONTROL_CONFIG['hostname']
-publisher_config = MQTT_CONTROL_CONFIG.copy()
-publisher_config['topic'] = MQTT_CONTROL_CONFIG['ctrl_topic']
-publisher_config.pop('ctrl_topic')
-
-print(colored(f"Sending commands control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker."))
-cmd_id = None
-received = False
-rdic = {}
-
-
-# set controller globally as __init__ seem to be called for each request and so we subscribe again each time (=overhead)
-controller = mqtt_client.Client(f"ohmpi_{OHMPI_CONFIG['id']}_interface_http", clean_session=False)  # create new instance
-print(colored(f"Connecting to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker", 'blue'))
-trials = 0
-trials_max = 10
-broker_connected = False
-while trials < trials_max:
-    try:
-        controller.username_pw_set(MQTT_CONTROL_CONFIG['auth'].get('username'),
-                                        MQTT_CONTROL_CONFIG['auth']['password'])
-        controller.connect(MQTT_CONTROL_CONFIG['hostname'])
-        trials = trials_max
-        broker_connected = True
-    except Exception as e:
-        print(f'Unable to connect control broker: {e}')
-        print('trying again to connect to control broker...')
-        time.sleep(2)
-        trials += 1
-if broker_connected:
-    print(f"Subscribing to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']}")
-    controller.subscribe(MQTT_CONTROL_CONFIG['ctrl_topic'], MQTT_CONTROL_CONFIG['qos'])
-else:
-    print(f"Unable to connect to control broker on {MQTT_CONTROL_CONFIG['hostname']}")
-    controller = None
-
-
-# start a listener for acknowledgement
-def _control():
-    def on_message(client, userdata, message):
-        global cmd_id, rdic, received
-
-        command = json.loads(message.payload.decode('utf-8'))
-        #print('++++', cmd_id, received, command)
-        if ('reply' in command.keys()) and (command['cmd_id'] == cmd_id):
-            print(f'Acknowledgement reception of command {command} by OhmPi')
-           # print('oooooooooook', command['reply'])
-            received = True
-            #rdic = command
-
-    controller.on_message = on_message
-    controller.loop_forever()
+# ctrl_broker = MQTT_CONTROL_CONFIG['hostname']
+# publisher_config = MQTT_CONTROL_CONFIG.copy()
+# publisher_config['topic'] = MQTT_CONTROL_CONFIG['ctrl_topic']
+# publisher_config.pop('ctrl_topic')
+
+# print(colored(f"Sending commands control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker."))
+# cmd_id = None
+# received = False
+# rdic = {}
+
+
+# # set controller globally as __init__ seem to be called for each request and so we subscribe again each time (=overhead)
+# controller = mqtt_client.Client(f"ohmpi_{OHMPI_CONFIG['id']}_interface_http", clean_session=False)  # create new instance
+# print(colored(f"Connecting to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker", 'blue'))
+# trials = 0
+# trials_max = 10
+# broker_connected = False
+# while trials < trials_max:
+#     try:
+#         controller.username_pw_set(MQTT_CONTROL_CONFIG['auth'].get('username'),
+#                                         MQTT_CONTROL_CONFIG['auth']['password'])
+#         controller.connect(MQTT_CONTROL_CONFIG['hostname'])
+#         trials = trials_max
+#         broker_connected = True
+#     except Exception as e:
+#         print(f'Unable to connect control broker: {e}')
+#         print('trying again to connect to control broker...')
+#         time.sleep(2)
+#         trials += 1
+# if broker_connected:
+#     print(f"Subscribing to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']}")
+#     controller.subscribe(MQTT_CONTROL_CONFIG['ctrl_topic'], MQTT_CONTROL_CONFIG['qos'])
+# else:
+#     print(f"Unable to connect to control broker on {MQTT_CONTROL_CONFIG['hostname']}")
+#     controller = None
+
+
+# # start a listener for acknowledgement
+# def _control():
+#     def on_message(client, userdata, message):
+#         global cmd_id, rdic, received
+
+#         command = json.loads(message.payload.decode('utf-8'))
+#         if ('reply' in command.keys()) and (command['cmd_id'] == cmd_id):
+#             print(f'Acknowledgement reception of command {command} by OhmPi')
+#             received = True
+
+#     controller.on_message = on_message
+#     controller.loop_forever()
     
-t = threading.Thread(target=_control)
-t.start()
+# t = threading.Thread(target=_control)
+# t.start()
 
 
 class MyServer(SimpleHTTPRequestHandler):
@@ -124,64 +121,64 @@ class MyServer(SimpleHTTPRequestHandler):
         cmd_id = uuid.uuid4().hex
         dic = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
         rdic = {} # response dictionary
-        if dic['cmd'] == 'run_multiple_sequences':
-            payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_multiple_sequences'})
-            publish.single(payload=payload, **publisher_config)
-        elif dic['cmd'] == 'interrupt':
-            payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'interrupt'})
-            publish.single(payload=payload, **publisher_config)
-        elif dic['cmd'] == 'getData':
-            # get all .csv file in data folder
-            fnames = [fname for fname in os.listdir('data/') if fname[-4:] == '.csv']
-            ddic = {}
-            for fname in fnames:
-                if ((fname != 'readme.txt')
-                    and ('_rs' not in fname)
-                    and (fname.replace('.csv', '') not in dic['surveyNames'])):
-                    df = pd.read_csv('data/' + fname)
-                    ddic[fname.replace('.csv', '')] = {
-                        'a': df['A'].tolist(),
-                        'b': df['B'].tolist(),
-                        'm': df['M'].tolist(),
-                        'n': df['N'].tolist(),
-                        'rho': df['R [ohm]'].tolist(),
-                    }
-            rdic['data'] = ddic
-        elif dic['cmd'] == 'removeData':
-            shutil.rmtree('data')
-            os.mkdir('data')
-        elif dic['cmd'] == 'update_settings':
-            if 'sequence' in dic['config'].keys() and dic['config']['sequence'] is not None:
-                sequence = dic['config'].pop('sequence', None)
-                sequence = np.loadtxt(StringIO(sequence)).astype(int).tolist()  # list of list
-                # we pass the sequence as a list of list as this object is easier to parse for the json.loads()
-                # of ohmpi._process_commands()
-                payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'kwargs': {'sequence': sequence}})
-                print('payload ===', payload)
-                publish.single(payload=payload, **publisher_config)
-            payload = json.dumps({'cmd_id': cmd_id + '_settings', 'cmd': 'update_settings', 'kwargs': {'config': dic['config']}})
-            cdic = dic['config']
-            publish.single(payload=payload, **publisher_config)
-        elif dic['cmd'] == 'invert':
-            pass
-        elif dic['cmd'] == 'getResults':
-            pass
-        elif dic['cmd'] == 'rsCheck':
-            payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'rs_check'})
-            publish.single(payload=payload, **publisher_config)
-
-        elif dic['cmd'] == 'getRsCheck':
-            fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv'])
-            if len(fnames) > 0:
-                df = pd.read_csv('data/' + fnames[-1])
-                ddic = {
-                    'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(),
-                    'res': df['RS [kOhm]'].tolist()
-                }
-            else:
-                ddic = {}
-            rdic['data'] = ddic
-        elif dic['cmd'] == 'download':
+        # if dic['cmd'] == 'run_multiple_sequences':
+        #     payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_multiple_sequences'})
+        #     publish.single(payload=payload, **publisher_config)
+        # elif dic['cmd'] == 'interrupt':
+        #     payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'interrupt'})
+        #     publish.single(payload=payload, **publisher_config)
+        # elif dic['cmd'] == 'getData':
+        #     # get all .csv file in data folder
+        #     fnames = [fname for fname in os.listdir('data/') if fname[-4:] == '.csv']
+        #     ddic = {}
+        #     for fname in fnames:
+        #         if ((fname != 'readme.txt')
+        #             and ('_rs' not in fname)
+        #             and (fname.replace('.csv', '') not in dic['surveyNames'])):
+        #             df = pd.read_csv('data/' + fname)
+        #             ddic[fname.replace('.csv', '')] = {
+        #                 'a': df['A'].tolist(),
+        #                 'b': df['B'].tolist(),
+        #                 'm': df['M'].tolist(),
+        #                 'n': df['N'].tolist(),
+        #                 'rho': df['R [ohm]'].tolist(),
+        #             }
+        #     rdic['data'] = ddic
+        # elif dic['cmd'] == 'removeData':
+        #     shutil.rmtree('data')
+        #     os.mkdir('data')
+        # elif dic['cmd'] == 'update_settings':
+        #     if 'sequence' in dic['config'].keys() and dic['config']['sequence'] is not None:
+        #         sequence = dic['config'].pop('sequence', None)
+        #         sequence = np.loadtxt(StringIO(sequence)).astype(int).tolist()  # list of list
+        #         # we pass the sequence as a list of list as this object is easier to parse for the json.loads()
+        #         # of ohmpi._process_commands()
+        #         payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'kwargs': {'sequence': sequence}})
+        #         print('payload ===', payload)
+        #         publish.single(payload=payload, **publisher_config)
+        #     payload = json.dumps({'cmd_id': cmd_id + '_settings', 'cmd': 'update_settings', 'kwargs': {'config': dic['config']}})
+        #     cdic = dic['config']
+        #     publish.single(payload=payload, **publisher_config)
+        # elif dic['cmd'] == 'invert':
+        #     pass
+        # elif dic['cmd'] == 'getResults':
+        #     pass
+        # elif dic['cmd'] == 'rsCheck':
+        #     payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'rs_check'})
+        #     publish.single(payload=payload, **publisher_config)
+
+        # elif dic['cmd'] == 'getRsCheck':
+        #     fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv'])
+        #     if len(fnames) > 0:
+        #         df = pd.read_csv('data/' + fnames[-1])
+        #         ddic = {
+        #             'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(),
+        #             'res': df['RS [kOhm]'].tolist()
+        #         }
+        #     else:
+        #         ddic = {}
+        #     rdic['data'] = ddic
+        if dic['cmd'] == 'download':
             shutil.make_archive('data', 'zip', 'data')
         elif dic['cmd'] == 'shutdown':
             print('shutting down...')
diff --git a/ohmpi/ohmpi.py b/ohmpi/ohmpi.py
index 0fe6a95ccc635aa9aa5ae598b49897521aa62d22..ac661493c6b6ee1276b3e98babd26f1775bc8991 100644
--- a/ohmpi/ohmpi.py
+++ b/ohmpi/ohmpi.py
@@ -15,7 +15,7 @@ from copy import deepcopy
 import numpy as np
 import csv
 import time
-from shutil import rmtree
+from shutil import rmtree, make_archive
 from threading import Thread
 from inspect import getmembers, isfunction
 from datetime import datetime
@@ -38,7 +38,7 @@ except Exception as error:
     print(colored(f'Unexpected error: {error}', 'red'))
     arm64_imports = None
 
-VERSION = '3.0.0-alpha'
+VERSION = '3.0.0-beta'
 
 
 class OhmPi(object):
@@ -273,10 +273,6 @@ class OhmPi(object):
                     if header == 'R [ohm]':
                         headers[i] = 'R [Ohm]'
                 icols = list(np.where(np.in1d(headers, ['A', 'B', 'M', 'N', 'R [Ohm]']))[0])
-                print(headers)
-                print('+++++', icols)
-                print(np.array(headers)[np.array(icols)])
-                print(np.loadtxt(os.path.join(ddir, fname), delimiter=',', skiprows=1, usecols=icols).shape)
                 data = np.loadtxt(os.path.join(ddir, fname), delimiter=',',
                                     skiprows=1, usecols=icols)                    
                 data = data[None, :] if len(data.shape) == 1 else data
@@ -307,6 +303,7 @@ class OhmPi(object):
             self.exec_logger.debug('Interrupted sequence acquisition...')
         else:
             self.exec_logger.debug('No sequence measurement thread to interrupt.')
+        self.status = 'idle'
         self.exec_logger.debug(f'Status: {self.status}')
 
     def load_sequence(self, filename: str, cmd_id=None):
@@ -353,7 +350,7 @@ class OhmPi(object):
         message : str
             message containing a command and arguments or keywords and arguments
         """
-        status = False
+        self.status = 'idle'
         cmd_id = '?'
         try:
             decoded_message = json.loads(message)
@@ -374,12 +371,10 @@ class OhmPi(object):
                 except Exception as e:
                     self.exec_logger.error(
                         f"Unable to execute {cmd}({str(kwargs) if kwargs is not None else ''}): {e}")
-                    status = False
         except Exception as e:
             self.exec_logger.warning(f'Unable to decode command {message}: {e}')
-            status = False
         finally:
-            reply = {'cmd_id': cmd_id, 'status': status}
+            reply = {'cmd_id': cmd_id, 'status': self.status}
             reply = json.dumps(reply)
             self.exec_logger.debug(f'Execution report: {reply}')
 
@@ -440,9 +435,31 @@ class OhmPi(object):
         else:
             self.exec_logger.warning('Not on Raspberry Pi, skipping reboot...')
 
+    def download_data(self, cmd_id=None):
+        """Create a zip of the data folder.
+        """
+        datadir = os.path.join(os.path.dirname(__file__), '../data/')
+        make_archive(datadir, 'zip', 'data')
+        self.data_logger.info(json.dumps({'download': 'ready'}))
+
+    def shutdown(self, cmd_id=None):
+        """Shutdown the Raspberry Pi
+
+        Parameters
+        ----------
+        cmd_id : str, optional
+            Unique command identifier
+        """
+
+        if self.on_pi:
+            self.exec_logger.info(f'Restarting pi following command {cmd_id}...')
+            os.system('poweroff')
+        else:
+            self.exec_logger.warning('Not on Raspberry Pi, skipping shutdown...')
+
     def run_measurement(self, quad=None, nb_stack=None, injection_duration=None, duty_cycle=None,
                         autogain=True, strategy='constant', tx_volt=5., best_tx_injtime=0.1,
-                        cmd_id=None, **kwargs):
+                        cmd_id=None, vmn_max=None, **kwargs):
         # TODO: add sampling_interval -> impact on _hw.rx.sampling_rate (store the current value, change the _hw.rx.sampling_rate, do the measurement, reset the sampling_rate to the previous value)
         # TODO: default value of tx_volt and other parameters set to None should be given in config.py and used in function definition
         # TODO: add rs_check option (or propose an other way to do this)
@@ -500,7 +517,7 @@ class OhmPi(object):
         bypass_check = kwargs['bypass_check'] if 'bypass_check' in kwargs.keys() else False
         d = {}
         if self.switch_mux_on(quad, bypass_check=bypass_check, cmd_id=cmd_id):
-            tx_volt = self._hw._compute_tx_volt(tx_volt=.5, strategy=strategy, vab_max=10.)  # TODO: use tx_volt and vmn_max instead of hardcoded values
+            tx_volt = self._hw._compute_tx_volt(tx_volt=tx_volt, strategy=strategy, vmn_max=vmn_max)  # TODO: use tx_volt and vmn_max instead of hardcoded values
             self._hw.vab_square_wave(tx_volt, cycle_duration=injection_duration*2/duty_cycle, cycles=nb_stack, duty_cycle=duty_cycle)
             if 'delay' in kwargs.keys():
                 delay = kwargs['delay']
@@ -559,6 +576,9 @@ class OhmPi(object):
 
             dd['cmd_id'] = str(cmd_id)
             self.data_logger.info(dd)
+            self._hw.switch_mux(electrodes=quad[0:2], roles=['A', 'B'], state='on')
+            time.sleep(0.2)
+            self._hw.switch_mux(electrodes=quad[0:2], roles=['A', 'B'], state='off')
 
         else:
             self.exec_logger.info(f'Skipping {quad}')
@@ -596,6 +616,12 @@ class OhmPi(object):
         self.exec_logger.debug(f'Status: {self.status}')
         self.exec_logger.debug(f'Measuring sequence: {self.sequence}')
 
+        # # kill previous running thread
+        # if self.thread is not None:
+        #     self.exec_logger.info('Removing previous thread')
+        #     self.thread.stop()
+        #     self.thread.join()
+
         def func():
             for g in range(0, nb_meas):  # for time-lapse monitoring
                 if self.status == 'stopping':
@@ -603,12 +629,13 @@ class OhmPi(object):
                     break
                 t0 = time.time()
                 self.run_sequence(**kwargs)
-
                 # sleeping time between sequence
                 dt = sequence_delay - (time.time() - t0)
                 if dt < 0:
                     dt = 0
                 if nb_meas > 1:
+                    if self.status == 'stopping':
+                        break
                     time.sleep(dt)  # waiting for next measurement (time-lapse)
             self.status = 'idle'
 
@@ -707,9 +734,11 @@ class OhmPi(object):
             # save data and print in a text file
             self.append_and_save(filename, acquired_data)
             self.exec_logger.debug(f'quadrupole {i + 1:d}/{n:d}')
-
         self._hw.pwr_state = 'off'
-        self.status = 'idle'
+
+        # reset to idle if we didn't interrupt the sequence
+        if self.status != 'stopping':
+            self.status = 'idle'
 
     def run_sequence_async(self, cmd_id=None, **kwargs):
         """Runs the sequence in a separate thread. Can be stopped by 'OhmPi.interrupt()'.
@@ -733,7 +762,9 @@ class OhmPi(object):
     #  -> might be a problem at B (cf what we did with WofE)
     def rs_check(self, tx_volt=5., cmd_id=None):
         # TODO: add a default value for rs-check in config.py import it in ohmpi.py and add it in rs_check definition
-        """Checks contact resistances
+        """Checks contact resistances.
+        Strategy: we just open A and B, measure the current and using vAB set or
+        assumed (12V assumed for battery), we compute Rab.
 
         Parameters
         ----------
@@ -748,7 +779,7 @@ class OhmPi(object):
         # create custom sequence where MN == AB
         # we only check the electrodes which are in the sequence (not all might be connected)
         if self.sequence is None:
-            quads = np.array([[1, 2, 1, 2]], dtype=np.uint32)
+            quads = np.array([[1, 2, 0, 0]], dtype=np.uint32)
         else:
             elec = np.sort(np.unique(self.sequence.flatten()))  # assumed order
             quads = np.vstack([
@@ -773,27 +804,40 @@ class OhmPi(object):
         # measure all quad of the RS sequence
         for i in range(0, quads.shape[0]):
             quad = quads[i, :]  # quadrupole
-            self.switch_mux_on(quad, bypass_check=True)  # put before raising the pins (otherwise conflict i2c)
-            d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=0.2, tx_volt=tx_volt, autogain=False,
-                                     bypass_check=True)
+            self._hw.switch_mux(electrodes=list(quads[i, :2]), roles=['A', 'B'], state='on')
+            self._hw._vab_pulse(duration=0.2)
+            current = self._hw.readings[-1, 3]
+            voltage = self._hw.tx.pwr.voltage * 1000
+            time.sleep(0.2)
+
+            # self.switch_mux_on(quad, bypass_check=True)  # put before raising the pins (otherwise conflict i2c)
+            # d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=0.2, tx_volt=tx_volt, autogain=False,
+            #                          bypass_check=True)
 
             # if self._hw.tx.voltage_adjustable:
             #     voltage = self._hw.tx.voltage  # imposed voltage on dps
             # else:
             #     voltage = self._hw.rx.voltage
 
-            voltage = self._hw.rx.voltage
-            current = self._hw.tx.current
+            # voltage = self._hw.rx.voltage
+            # current = self._hw.tx.current
 
             # compute resistance measured (= contact resistance)
-            resist = abs(voltage / current) / 1000.
+            resist = abs(voltage / current) / 1000 # kOhm
             # print(str(quad) + '> I: {:>10.3f} mA, V: {:>10.3f} mV, R: {:>10.3f} kOhm'.format(
             #    current, voltage, resist))
-            msg = f'Contact resistance {str(quad):s}: I: {current * 1000.:>10.3f} mA, ' \
-                  f'V: {voltage :>10.3f} mV, ' \
-                  f'R: {resist :>10.3f} kOhm'
-
-            self.exec_logger.info(msg)
+            # msg = f'Contact resistance {str(quad):s}: I: {current :>10.3f} mA, ' \
+            #       f'V: {voltage :>10.3f} mV, ' \
+            #       f'R: {resist :>10.3f} kOhm'
+            # create a message as dictionnary to be used by the html interface
+            msg = {
+                'rsdata': {
+                    'A': int(quad[0]),
+                    'B': int(quad[1]),
+                    'rs': resist,  # in kOhm
+                }
+            }
+            self.data_logger.info(json.dumps(msg))
 
             # if contact resistance = 0 -> we have a short circuit!!
             if resist < 1e-5:
@@ -829,10 +873,8 @@ class OhmPi(object):
         try:
             self.sequence = np.array(sequence).astype(int)
             # self.sequence = np.loadtxt(StringIO(sequence)).astype('uint32')
-            status = True
         except Exception as e:
             self.exec_logger.warning(f'Unable to set sequence: {e}')
-            status = False
 
     def switch_mux_on(self, quadrupole, bypass_check=False, cmd_id=None):
         """Switches on multiplexer relays for given quadrupole.
@@ -844,7 +886,7 @@ class OhmPi(object):
         quadrupole : list of 4 int
             List of 4 integers representing the electrode numbers.
         bypass_check: bool, optional
-            Bypasses checks for A==M or A==M or B==M or B==N (i.e. used for rs-check)
+            Bypasses checks for A==M or A==N or B==M or B==N (i.e. used for rs-check)
         """
         assert len(quadrupole) == 4
         if (self._hw.tx.pwr.voltage > self._hw.rx._voltage_max) and bypass_check:
@@ -921,7 +963,6 @@ class OhmPi(object):
         cmd_id : str, optional
             Unique command identifier
         """
-        status = False
         if settings is not None:
             try:
                 if isinstance(settings, dict):
@@ -933,10 +974,10 @@ class OhmPi(object):
                         dic = json.load(json_file)
                     self.settings.update(dic)
                 self.exec_logger.debug('Acquisition parameters updated: ' + str(self.settings))
-                status = True
+                self.status = 'idle (acquisition updated)'
             except Exception as e:  # noqa
                 self.exec_logger.warning('Unable to update settings.')
-                status = False
+                self.status = 'idle (unable to update settings)'
         else:
             self.exec_logger.warning('Settings are missing...')
 
@@ -946,8 +987,6 @@ class OhmPi(object):
             self.settings['export_dir'] = os.path.split(self.settings['export_path'])[0]
             self.settings['export_name'] = os.path.split(self.settings['export_path'])[1]
 
-        return status
-
     def run_inversion(self, survey_names=[], elec_spacing=1, **kwargs):
         """Run a simple 2D inversion using ResIPy.
         
diff --git a/ohmpi_settings.json b/ohmpi_settings.json
index e839f5fb9a4ddef894a5a8b714757b71cd860c02..2964e24b850157b3fe3a449a4cf5e62a701f82f3 100644
--- a/ohmpi_settings.json
+++ b/ohmpi_settings.json
@@ -1,5 +1,5 @@
 {
-    "nb_electrodes": 64,
+    "nb_electrodes": 16,
     "injection_duration": 0.2,
     "nb_stack": 1,
     "nb_meas": 1,
diff --git a/run_http_interface.sh b/run_http_interface.sh
index 956067d067460056cf3d8ac18b6d519bc7413dfe..f98f082f9b176adf1a1a0afa429146ab9fb33c16 100755
--- a/run_http_interface.sh
+++ b/run_http_interface.sh
@@ -1,7 +1,7 @@
 #!bin/bash
-USER="pi"  # change if other username
+export PYTHONPATH=/home/$USER/OhmPi
 cd /home/$USER/OhmPi
 source /home/$USER/OhmPi/ohmpy/bin/activate
-python ohmpi.py &  # run ohmpi.py to capture the commands
-python http_interface.py  # run http_interface to serve the web GUI
+python dev/start_mqtt_html.py &  # run ohmpi.py to capture the commands
+python3 -m http.server  # run web GUI
 
diff --git a/sequences/test_circuit_1423.txt b/sequences/test_circuit_1423.txt
index 691d10eff3a18e8e8d92ec28d9a5aec2753f7d18..397bb332d915a5785e0f5b4f884ba5deaa584623 100644
--- a/sequences/test_circuit_1423.txt
+++ b/sequences/test_circuit_1423.txt
@@ -2,12 +2,4 @@
 2 5 3 4
 3 6 4 5
 4 7 5 6
-5 8 6 7
-6 9 7 8
-7 10 8 9
-8 11 9 10
-9 12 10 11
-10 13 11 12
-11 14 12 13
-12 15 13 14
-13 16 14 15
+