diff --git a/.gitignore b/.gitignore
index d660b3ade6bb036d44fd6f4b723765a414a1d6a3..411f65872b6b5acccde4445192ad665579f627b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
-data/*.csv
+data/*
 **/.ipynb_notebooks/**
 data.zip
 __pycache__
 Ohmpi_4elec_mqtt.py
+ohmpy/*
+logs/*
+sequence.txt
diff --git a/config.py b/config.py
index 90f74494c0c70744633c63180351c8f01e4ddadc..7062f15cf4bc8965b06dfdb1b81d03637d456b07 100644
--- a/config.py
+++ b/config.py
@@ -12,7 +12,8 @@ OHMPI_CONFIG = {
     'integer': 2,  # Max value 10 # TODO: Explain what this is...
     'version': 2,
     'max_elec': 64,
-    'board_address': {'A': 0x76, 'B': 0x71, 'M': 0x74, 'N': 0x70}  # def. {'A': 0x76, 'B': 0x71, 'M': 0x74, 'N': 0x70}
+    'board_address': {'A': 0x70, 'B': 0x71, 'M': 0x72, 'N': 0x73}  # def. {'A': 0x76, 'B': 0x71, 'M': 0x74, 'N': 0x70}
+    #'board_address': {'A': 0x76, 'B': 0x71, 'M': 0x74, 'N': 0x70}  # def. {'A': 0x76, 'B': 0x71, 'M': 0x74, 'N': 0x70}
 }
 
 # Execution logging configuration
@@ -48,7 +49,7 @@ SOH_LOGGING_CONFIG = {
 
 # MQTT logging configuration parameters
 MQTT_LOGGING_CONFIG = {
-    'hostname': 'ohmpy.umons.ac.be',
+    'hostname': 'raspberrypi.local',
     'port': 1883,
     'qos': 0,
     'retain': False,
diff --git a/data/readme.txt b/data/readme.txt
deleted file mode 100644
index f311a0eec0aa8ba6ccee55358087481c1b89005c..0000000000000000000000000000000000000000
--- a/data/readme.txt
+++ /dev/null
@@ -1 +0,0 @@
-In this folder will be logged all measurements done with the OhmPi as .csv.
diff --git a/env.sh b/env.sh
old mode 100644
new mode 100755
index e79f68dfd58fdfd2e7d0a984708ef57c33471693..7f8f33f924942ce32cff08982c81987785e28184
--- a/env.sh
+++ b/env.sh
@@ -1,4 +1,4 @@
-#!/bin/bash 
+#!/bin/bash
 sudo apt-get install -y libatlas-base-dev
 python3 -m venv ohmpy
 source ohmpy/bin/activate || exit 1  # NOTE: Added || exit to avoid installing requirements in system python
diff --git a/index.html b/index.html
index 0207a38f5c4978460192d6331f14d2da250f1781..423e5591797f17d7fd9895a803029bce00cddc1d 100644
--- a/index.html
+++ b/index.html
@@ -1,427 +1,549 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf8"/>
-    <title>OhmPi Acquisition Board</title>
-    <link rel="shortcut icon" type="image/jpg" href="logo_ohmpi.jpg"/>
-    
-    <!-- dependencies (need to be local as no internet in AP mode)-->
-    <script src="js/plotly-basic-2.8.3.min.js"></script>
-    <script src="js/jquery-3.4.1.min.js"></script>
-    <link type="text/css" href="css/bootstrap.min.css" rel="stylesheet">
-	<!-- <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> -->
-	<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous"> -->
-    <!-- <script src="js/danfojs/bundle.min.js"></script> -->
-</head>
-<body>
-    <div class='container'>
-        <h1>OhmPi Acquisition Board</h1>
-        <!-- nb stacks, on-time -->
-        <button id="setConfigBtn" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#exampleModal">Settings</button>
-        <button id='startBtn' type="button" class="btn btn-primary">Start</button>
-        <button id='stopBtn' type="button" class="btn btn-warning">Stop</button>
-        <!-- upload button for csv which display the table ABMN -->
-        <button id="removeDataBtn" type="button" class="btn btn-danger">Clear data</button>
-        <button id="getDataBtn" type="button" class="btn btn-info">Get data</button>
-        <div class="form-check">
-            <input id="dataRetrievalCheck" class="form-check-input" type="checkbox" value="">
-            <label class="form-check-label" for="dataRetrievalCheck">
-                Automatically get data every 1 second
-            </label>
-        </div>
-        <div id='output'>Status: idle</div>
-        <select id='surveySelect' class='custom-select'>
-        </select>
-        <div id="gd"></div>
-        <div class="mb3 row">
-            <label for="quadSelect">Quadrupole:</label>
-            <div class="col-sm-10">
-                <select id='quadSelect' class='custom-select'></select>    
-            </div>
-        </div>
-        <button id="addTraceBtn" type="button" class="btn btn-info">Add trace</button>
-        <button id="removeTracesBtn" type="button" class="btn btn-info">Remove all traces</button>    
-        <div id="ts"></div>
-        <button id="downloadBtn" type="button" class="btn btn-primary">Download data</button>
-        <!-- <button id="invertBtn" type="button" class="btn btn-primary">Invert</button> -->
-        <a id="download"></a>
-
-        <!-- Modal for configuration -->
-        <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
-            <div class="modal-dialog" role="document">
-            <div class="modal-content">
-                <div class="modal-header">
-                <h5 class="modal-title" id="exampleModalLabel">OhmPi configuration</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&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>        
-                      </form>
-                </div>
-                <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
-                <button id="saveConfigBtn" type="button" data-dismiss="modal" class="btn btn-primary">Save</button>
-                </div>
-            </div>
-            </div>
-        </div>
-        
-        <footer>v0.2.0</footer>
-    </div>
-
-    <script type="text/javascript">
-        //let serverUrl = 'http://10.3.141.1:8080'
-        let serverUrl = 'http://0.0.0.0:8080'
-        //let serverUrl = 'http://localhost:8080'
-        let output = document.getElementById('output')
-        let data = {} // hold data of all surveys
-        let interv = null // hold interval for automatic data retrieval
-        let quads = [] // available quadrupoles for time-series figure
-        let squads = [] // selected quadrupoles for time-series figure
-
-        // useful functions
-        function sendCommand(query, callback=null) {
-            // dic in the form: {'command': X, ...} as JSON
-            if (callback == null) {
-                function callback(x) {
-                    console.log('default callback:', x)
-                }
-            }
-            let xhr = new XMLHttpRequest();
-            xhr.onreadystatechange = function() {
-                if (this.readyState == 4) {
-                    if (xhr.status == 200) {
-                        callback(JSON.parse(xhr.response))
-                    }
-                }
-            }
-            xhr.open('POST', serverUrl)
-            xhr.setRequestHeader('Content-Type', 'application/json')
-            xhr.send(query)
-        }
-
-        // start button
-        function startBtnFunc() {
-            sendCommand('{"command": "start"}', function(x) {
-                console.log(x['status'])
-                if (x['status'] == 'running') {
-                    output.innerHTML = 'Status: measuring...'
-                }
-            })
-        }
-        let startBtn = document.getElementById('startBtn')
-        startBtn.addEventListener('click', startBtnFunc)
-
-        // stop button
-        function stopBtnFunc() {
-            sendCommand('{"command": "stop"}', function(x) {
-                output.innerHTML = 'Status: ' + x['status']
-                clearInterval(interv)
-                getData()
-            })
-        }
-        let stopBtn = document.getElementById('stopBtn')
-        stopBtn.addEventListener('click', stopBtnFunc)
-
-        // set configuration
-        function saveConfigBtnFunc() {
-            // collect values from modal
-            let formVals = {}
-            for (let field of ['nbElectrodes', 'injectionDuration',
-             'nbMeasurements', 'sequenceDelay', 'nbStack']) {
-                formVals[field] = document.getElementById(field).value
-            }
-            console.log(formVals)
-
-            sendCommand(JSON.stringify({
-                'command': 'setConfig',
-                 'config': formVals
-            }), function(x) {
-                console.log('setconfig:', x)
-            })
-        }
-        let saveConfigBtn = document.getElementById('saveConfigBtn')
-        saveConfigBtn.addEventListener('click', saveConfigBtnFunc)
-
-        // make pseudo plot
-        var trace = {
-            x: [],
-            y: [],
-            mode: 'markers',
-            marker: {
-                size: 40,
-                color: [],
-                colorbar: {
-                    title: 'App. res. [Ohm.m]'
-                }
-            }
-        }
-        let layout = {
-            title: 'Pseudo-section',
-            yaxis: {
-                title: 'Pseudo-depth',
-                autorange: 'reversed'
-            },
-            xaxis: {
-                title: 'X'
-            }
-
-        }
-        Plotly.newPlot('gd', [trace], layout)
-
-        // make time-series plot
-        let tdata = []
-        let layout2 = {
-            title: 'Time series',
-            yaxis: {
-                title: 'App. res. [Ohm.m]'
-            },
-            xaxis: {
-                title: 'Sampling time'
-            }
-        }
-        Plotly.newPlot('ts', tdata, layout2)
-
-        // add trace to time-series 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-series plot
-        function removeTracesBtnFunc() {
-            squads = []
-            tdata = []            
-            Plotly.newPlot('ts', tdata, layout2)
-        }
-        let removeTracesBtn = document.getElementById('removeTracesBtn')
-        removeTracesBtn.addEventListener('click', removeTracesBtnFunc)
-
-        // getData
-        function getData() {
-            sendCommand(JSON.stringify({
-                'command': 'getData',
-                'surveyNames': Object.keys(data).slice(0, -1)
-                // last survey is often partial so we download it again
-            }), function(ddic) {
-                // update status
-                output.innerHTML = 'Status: ' + ddic['status']
-
-                // update data dic with new data
-                data = { // destructuring assignment (magic! :o)
-                    ...data,
-                    ...ddic['data'] // value from second dic are preferred
-                }
-                
-                // dropdown with number of surveys and +++
-                let surveyNames = Object.keys(data).sort()
-
-                // callback function to draw the plot
-                function surveySelectFunc(el) {
-                    let surveyName = el['target'].value
-                    let df = data[surveyName]
-                    if (df != undefined) {
-                        let a = df['a']
-                        let b = df['b']
-                        let m = df['m']
-                        let n = df['n']
-                        // let's assume electrodes are 1 m distance
-                        // compute pseudo-depth (assume no topo)
-                        let xpos = []
-                        let ypos = []
-                        for (let i = 0; i < a.length; i++) {
-                            let ab = (a[i] + b[i])/2
-                            let mn = (m[i] + n[i])/2
-                            let dist = Math.abs(ab - mn)
-                            xpos.push(Math.min(ab, mn) + dist/2)
-                            ypos.push(Math.sqrt(2)/2*dist)
-                        }
-                        // update the trace and redraw the figure
-                        trace['x'] = xpos
-                        trace['y'] = ypos
-                        trace['marker']['color'] = df['rho']
-                        Plotly.redraw('gd')
-                    }
-                }
-
-                let surveySelect = document.getElementById('surveySelect')
-                
-                // remove listener as we will replace the choices
-                surveySelect.removeEventListener('change', surveySelectFunc)
-                surveySelect.innerHTML = ''  // clearing all child nodes
-
-                // add choices again
-                for (let surveyName of surveyNames) {
-                    let option = document.createElement('option')
-                    option.innerText = surveyName
-                    option.value = surveyName
-                    surveySelect.appendChild(option)
-                }
-
-                // listener again
-                surveySelect.addEventListener('change', surveySelectFunc)
-                
-                // plot last one by default
-                surveySelect.value = surveyNames[surveyNames.length - 1]
-                
-                // call the function directly
-                // (as programmatically changing 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-series 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 dictionary
-                        tdata[k]['x'] = x
-                        tdata[k]['y'] = y
-                    }
-                    //console.log(tdata)
-                    Plotly.redraw('ts')
-                }
-            })
-        }
-        let getDataBtn = document.getElementById('getDataBtn')
-        getDataBtn.addEventListener('click', getData)
-        
-        // checkbox interaction for data download
-        function dataRetrievalCheckFunc(x) {
-            if (x['target'].checked == true) {
-                interv = setInterval(getData, 1000) // every 5s
-            } else {
-                clearInterval(interv)
-            }             
-        }
-        let dataRetrievalCheck = document.getElementById('dataRetrievalCheck')
-        dataRetrievalCheck.addEventListener('change', dataRetrievalCheckFunc)
-
-        // remove data
-        function removeDataBtnFunc() {
-            sendCommand('{"command": "removeData"}',function(x) {
-                data = {}
-                output.innerHTML = 'Status: ' + x['status'] + ' (all data cleared)'
-                console.log('all data removed')
-            })
-        }
-        let removeDataBtn = document.getElementById('removeDataBtn')
-        removeDataBtn.addEventListener('click', removeDataBtnFunc)
-
-        // invert data
-        // function invertBtnFunc() {
-        //     sendCommand('{"command": "invert"}', function(x) {
-        //         console.log('inversion results', x)
-        //     })
-        // }
-        // let invertBtn = document.getElementById('invertBtn')
-        // invertBtn.addEventListener('click', invertBtnFunc)
-
-        // download data
-        function downloadBtnFunc() {
-            sendCommand('{"command": "download"}', function(x) {
-                let dwl = document.getElementById('download')
-                dwl.setAttribute('href', serverUrl + '/data.zip')
-                dwl.setAttribute('download', 'data.zip')
-                dwl.click()
-            })
-        }
-        let downloadBtn = document.getElementById('downloadBtn')
-        downloadBtn.addEventListener('click', downloadBtnFunc)
-
-
-    </script>
-    
-    <!-- Boostrap scripts (at the end of the page for faster loading time)-->
-	<script src="js/bootstrap.bundle.min.js"></script>
-    <!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous"></script> -->
-</body>
-</html>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf8"/>
+    <title>OhmPi Acquisition Board</title>
+    <link rel="shortcut icon" type="image/jpg" href="logo_ohmpi.jpg"/>
+    
+    <!-- dependencies (need to be local as no internet in AP mode)-->
+    <script src="js/plotly-basic-2.8.3.min.js"></script>
+    <script src="js/jquery-3.4.1.min.js"></script>
+    <link type="text/css" href="css/bootstrap.min.css" rel="stylesheet">
+	<!-- <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> -->
+	<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous"> -->
+    <!-- <script src="js/danfojs/bundle.min.js"></script> -->
+</head>
+<body>
+    <div class='container'>
+        <h1>OhmPi Acquisition Board</h1>
+        <!-- nb stacks, on-time -->
+        <button id="setConfigBtn" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#exampleModal">Settings</button>
+        <button id='startBtn' type="button" class="btn btn-primary">Start</button>
+        <button id='stopBtn' type="button" class="btn btn-warning">Stop</button>
+        <!-- upload button for csv which display the table ABMN -->
+        <button id="removeDataBtn" type="button" class="btn btn-danger">Clear data</button>
+        <button id="getDataBtn" type="button" class="btn btn-info">Get data</button>
+        <div class="form-check">
+            <input id="dataRetrievalCheck" class="form-check-input" type="checkbox" value="">
+            <label class="form-check-label" for="dataRetrievalCheck">
+                Automaticaly get data every 1 second
+            </label>
+        </div>
+        <div id='output'>Status: idle</div>
+        
+        <!-- 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">&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>           
+                      </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
+
+        // useful functions
+        function sendCommand(query, callback=null) {
+            // dic in the form: {'command': X, ...} as JSON
+            if (callback == null) {
+                function callback(x) {
+                    console.log('default callback:', x)
+                }
+            }
+            let xhr = new XMLHttpRequest();
+            xhr.onreadystatechange = function() {
+                if (this.readyState == 4) {
+                    if (xhr.status == 200) {
+                        callback(JSON.parse(xhr.response))
+                    }
+                }
+            }
+            xhr.open('POST', serverUrl)
+            xhr.setRequestHeader('Content-Type', 'application/json')
+            xhr.send(query)
+        }
+
+        // start button
+        function startBtnFunc() {
+            sendCommand('{"command": "start"}', function(x) {
+                console.log(x['status'])
+                if (x['status'] == 'running') {
+                    output.innerHTML = 'Status: measuring...'
+                }
+            })
+        }
+        let startBtn = document.getElementById('startBtn')
+        startBtn.addEventListener('click', startBtnFunc)
+
+        // stop button
+        function stopBtnFunc() {
+            sendCommand('{"command": "stop"}', function(x) {
+                output.innerHTML = 'Status: ' + x['status']
+                clearInterval(interv)
+                getData()
+            })
+        }
+        let stopBtn = document.getElementById('stopBtn')
+        stopBtn.addEventListener('click', stopBtnFunc)
+
+        // set configuration
+        function saveConfigBtnFunc() {
+            // collect values from modal
+            let formVals = {}
+            for (let field of ['nbElectrodes', 'injectionDuration',
+             'nbMeasurements', 'sequenceDelay', 'nbStack']) {
+                formVals[field] = document.getElementById(field).value
+            }
+            console.log(formVals)
+            
+            // define callback to send settigs to Pi
+            function configCallback() {
+                sendCommand(JSON.stringify({
+                    'command': 'setConfig',
+                     'config': formVals
+                }), function(x) {
+                    console.log('setconfig:', 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('{"command": "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({
+                'command': 'getData',
+                'surveyNames': Object.keys(data).slice(0, -1)
+                // last survey is often partial so we download it again
+            }), function(ddic) {
+                // update status
+                output.innerHTML = 'Status: ' + ddic['status']
+
+                // update data dic with new data
+                data = { // destructuring assignement (magic! :o)
+                    ...data,
+                    ...ddic['data'] // value from second dic are preferred
+                }
+                
+                // dropdown with number of surveys and +++
+                let surveyNames = Object.keys(data).sort()
+
+                // 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('{"command": "removeData"}',function(x) {
+                data = {}
+                output.innerHTML = 'Status: ' + x['status'] + ' (all data cleared)'
+                console.log('all data removed')
+            })
+        }
+        let removeDataBtn = document.getElementById('removeDataBtn')
+        removeDataBtn.addEventListener('click', removeDataBtnFunc)
+
+        // shutdown Pi
+        function shutdownBtnFunc() {
+            sendCommand('{"command": "shutdown"}', function(x) {
+                console.log('shuting down...')
+            })
+        }
+        let shutdownBtn = document.getElementById('shutdownBtn')
+        shutdownBtn.addEventListener('click', shutdownBtnFunc)
+        
+        // restart Pi
+        function restartBtnFunc() {
+            sendCommand('{"command": "restart"}', function(x) {
+                console.log('rebooting...')
+            })
+        }
+        let restartBtn = document.getElementById('restartBtn')
+        restartBtn.addEventListener('click', restartBtnFunc)
+        
+        // invert data
+        // function invertBtnFunc() {
+        //     sendCommand('{"command": "invert"}', function(x) {
+        //         console.log('inversion results', x)
+        //     })
+        // }
+        // let invertBtn = document.getElementById('invertBtn')
+        // invertBtn.addEventListener('click', invertBtnFunc)
+
+        // download data
+        function downloadBtnFunc() {
+            sendCommand('{"command": "download"}', function(x) {
+                let dwl = document.getElementById('download')
+                dwl.setAttribute('href', serverUrl + '/data.zip')
+                dwl.setAttribute('download', 'data.zip')
+                dwl.click()
+            })
+        }
+        let downloadBtn = document.getElementById('downloadBtn')
+        downloadBtn.addEventListener('click', downloadBtnFunc)
+
+
+    </script>
+    
+    <!-- Boostrap scripts (at the end of the page for faster loading time)-->
+	<script src="js/bootstrap.bundle.min.js"></script>
+    <!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous"></script> -->
+</body>
+</html>
diff --git a/install_local_mqtt_broker.sh b/install_local_mqtt_broker.sh
old mode 100644
new mode 100755
diff --git a/ohmpi.py b/ohmpi.py
index 44450d3409f0db34c2aa5581d39a483bae293830..6dfe8329471d8f4ae3f4e359d6415cc62f321f37 100644
--- a/ohmpi.py
+++ b/ohmpi.py
@@ -120,7 +120,7 @@ class OhmPi(object):
             - injection_duration (in seconds)
             - nbr_meas (total number of times the sequence will be run)
             - sequence_delay (delay in second between each sequence run)
-            - stack (number of stack for each quadrupole measurement)
+            - nb_stack (number of stack for each quadrupole measurement)
             - export_path (path where to export the data, timestamp will be added to filename)
 
         Parameters
@@ -375,7 +375,7 @@ class OhmPi(object):
         # TODO here we can add the current_injected or voltage_injected in mA or mV
         # check arguments
         if nb_stack is None:
-            nb_stack = self.pardict['stack']
+            nb_stack = self.pardict['nb_stack']
         if injection_duration is None:
             injection_duration = self.pardict['injection_duration']
 
@@ -410,8 +410,7 @@ class OhmPi(object):
         gain_voltage = self.gain_auto(AnalogIn(self.ads_voltage, ads.P0, ads.P1))
         pin0.value = False
         pin1.value = False
-        print(gain_current)
-        print(gain_voltage)
+        print('gain current: {:.3f}, gain voltage: {:.3f}'.format(gain_current, gain_voltage))
         self.ads_current = ads.ADS1115(self.i2c, gain=gain_current, data_rate=860, address=0x48)
         self.ads_voltage = ads.ADS1115(self.i2c, gain=gain_voltage, data_rate=860, address=0x49)
 
@@ -465,20 +464,20 @@ class OhmPi(object):
 
         # create a dictionary and compute averaged values from all stacks
         d = {
-            "time": [datetime.now().isoformat()],
+            "time": datetime.now().isoformat(),
             "A": quad[0],
             "B": quad[1],
             "M": quad[2],
             "N": quad[3],
             "inj time [ms]": (end_delay - start_delay) * 1000,
-            "Vmn [mV]": [(sum_vmn / (3 + 2 * nb_stack - 1))],
-            "I [mA]": [(injection_current / (3 + 2 * nb_stack - 1))],
-            "R [ohm]": [(sum_vmn / (3 + 2 * nb_stack - 1) / (injection_current / (3 + 2 * nb_stack - 1)))],
-            "Ps [mV]": [(sum_ps / (3 + 2 * nb_stack - 1))],
-            "nbStack": [nb_stack],
-            "CPU temp [degC]": [CPUTemperature().temperature],
-            "Time [s]": [(-start_time + time.time())],
-            "Nb samples [-]": [self.nb_samples]
+            "Vmn [mV]": (sum_vmn / (3 + 2 * nb_stack - 1)),
+            "I [mA]": (injection_current / (3 + 2 * nb_stack - 1)),
+            "R [ohm]": (sum_vmn / (3 + 2 * nb_stack - 1) / (injection_current / (3 + 2 * nb_stack - 1))),
+            "Ps [mV]": (sum_ps / (3 + 2 * nb_stack - 1)),
+            "nbStack": nb_stack,
+            "CPU temp [degC]": CPUTemperature().temperature,
+            "Time [s]": (-start_time + time.time()),
+            "Nb samples [-]": self.nb_samples
         }
 
         # round number to two decimal for nicer string output
@@ -500,18 +499,45 @@ class OhmPi(object):
         """ Check contact resistance.
         """
         # create custom sequence where MN == AB
-        nelec = self.sequence.max()  # number of elec used in the sequence
+        # we only check the electrodes which are in the sequence (not all might be connected)
+        elec = np.sort(np.unique(self.sequence.flatten())) # assumed order
         quads = np.vstack([
-            np.arange(nelec - 1) + 1,
-            np.arange(nelec - 1) + 2,
-            np.arange(nelec - 1) + 1,
-            np.arange(nelec - 1) + 2
+            elec[:-1],
+            elec[1:],
+            elec[:-1],
+            elec[1:],
         ]).T
+        
+        # create filename to store RS
+        export_path_rs = self.pardict['export_path'].replace('.csv', '') \
+                      + '_' + datetime.now().strftime('%Y%m%dT%H%M%S') + '_rs.csv'
 
+        # perform RS check
+        self.run = True
+        self.status = 'running'
+        
+        # make sure all mux are off to start with
+        self.reset_mux()
+
+        # measure all quad of the RS sequence
         for i in range(0, quads.shape[0]):
             quad = quads[i, :]  # quadrupole
-            self.reset_mux()
-            self.switch_mux_on(quad)
+            
+            # NOTE (GB): I'd use the self.run_measurement() for all this middle part so we an make use of autogain and so ...
+            # call the switch_mux function to switch to the right electrodes
+            #self.switch_mux_on(quad)
+
+            # run a measurement
+            #current_measurement = self.run_measurement(quad, 1, 0.25)
+            
+            # switch mux off
+            #self.switch_mux_off(quad)
+
+            # save data and print in a text file
+            #self.append_and_save(export_path_rs, current_measurement)
+            
+            
+            # current injection
             pin0 = self.mcp.get_pin(0)
             pin0.direction = Direction.OUTPUT
             pin1 = self.mcp.get_pin(1)
@@ -519,40 +545,51 @@ class OhmPi(object):
             pin0.value = False
             pin1.value = False
 
-            print(quad)
             # call the switch_mux function to switch to the right electrodes
+            self.switch_mux_on(quad)
             self.ads_current = ads.ADS1115(self.i2c, gain=2 / 3, data_rate=860, address=0x48)
             # ADS1115 for voltage measurement (MN)
             self.ads_voltage = ads.ADS1115(self.i2c, gain=2 / 3, data_rate=860, address=0x49)
-            pin1.value = True
+            pin1.value = True  # inject from pin1 to pin0
             pin0.value = False
             time.sleep(0.2)
+            
+            # measure current and voltage
             current = AnalogIn(self.ads_current, ads.P0).voltage / (50 * self.r_shunt)
             voltage = -AnalogIn(self.ads_voltage, ads.P0, ads.P1).voltage * 2.5
             resistance = voltage / current
-            # print(B)
-            # print(A)
-            print(abs(round(resistance / 1000, 1)), "kOhm")
+            
+            # compute resistance measured (= contact resistance)
+            resist = abs(resistance / 1000)
+            msg = 'Contact resistance {:s}: {:.3f} kOhm'.format(
+                str(quad), resist)
+            print(msg)
+            self.exec_logger.debug(msg)
+            
+            
+            # if contact resistance = 0 -> we have a short circuit!!
+            if resist < 1e-5:
+                msg = '!!!SHORT CIRCUIT!!! {:s}: {:.3f} kOhm'.format(
+                    str(quad), resist)
+                self.exec_logger.warning(msg)
+                print(msg)
+                
+            # save data and print in a text file
+            self.append_and_save(export_path_rs, {
+                'A': quad[0],
+                'B': quad[1],
+                'RS [kOhm]': resist,
+            })
+            
+            # close mux path and put pin back to GND
             self.switch_mux_off(quad)
             pin0.value = False
             pin1.value = False
+            
+        self.reset_mux()
+        self.status = 'idle'
+        self.run = False
 
-    #         # create backup TODO not good
-    #         export_path = self.pardict['export_path']
-    #         sequence = self.sequence.copy()
-    #
-    #         # assign new value
-    #         self.pardict['export_path'] = export_path.replace('.csv', '_rs.csv')
-    #         self.sequence = quads
-    #         print(self.sequence)
-    #
-    #         # run the RS check
-    #         self.log_exec('RS check (check contact resistance)', level='debug')
-    #         self.measure()
-    #
-    #         # restore
-    #         self.pardict['export_path'] = export_path
-    #         self.sequence = sequence
     #
     #         # TODO if interrupted, we would need to restore the values
     #         # TODO or we offer the possiblity in 'run_measurement' to have rs_check each time?
@@ -616,7 +653,7 @@ class OhmPi(object):
 
                     # run a measurement
                     if self.on_pi:
-                        current_measurement = self.run_measurement(quad, self.pardict["stack"],
+                        current_measurement = self.run_measurement(quad, self.pardict["nb_stack"],
                                                                    self.pardict["injection_duration"])
                     else:  # for testing, generate random data
                         current_measurement = {
@@ -680,7 +717,7 @@ if on_pi:
     #       and emit a warning otherwise
     if not arm64_imports:
         print(colored(f'Warning: Required packages are missing.\n'
-                      f'Please run . env.sh at command prompt to update your virtual environment\n', 'yellow'))
+                      f'Please run ./env.sh at command prompt to update your virtual environment\n', 'yellow'))
 else:
     print(colored(f'Not running on the Raspberry Pi platform.\nFor simulation purposes only...', 'yellow'))
 
diff --git a/ohmpi_param.json b/ohmpi_param.json
index 7071487a858d80f5fa2bec82abde9eec12c3a328..c2c68558a80f39841ea5d17858a2900ca4f9d454 100644
--- a/ohmpi_param.json
+++ b/ohmpi_param.json
@@ -3,6 +3,6 @@
     "injection_duration":0.2,
     "nbr_meas": 100,
     "sequence_delay": 1,
-    "stack": 1,
+    "nb_stack": 1,
     "export_path": "data/measurement.csv" 
 }
diff --git a/run.sh b/run.sh
old mode 100644
new mode 100755
index 61eb5c72bd2127c03a2529bde5a92e34b49bdc98..76b58dca433be66edff7bd1c39cb4323939ede9a
--- a/run.sh
+++ b/run.sh
@@ -1,2 +1,2 @@
 source ./ohmpy/bin/activate
-python3 webserver.py
+python webserver.py
diff --git a/runOnStart.sh b/runOnStart.sh
new file mode 100755
index 0000000000000000000000000000000000000000..801ecbaa9986b26597ce0e4facb98326cf3e5225
--- /dev/null
+++ b/runOnStart.sh
@@ -0,0 +1,2 @@
+echo "# start OhmPi web interface" >> $HOME/.bashrc
+echo "(cd $HOME/OhmPi; ./run.sh)" >> $HOME/.bashrc
diff --git a/webserver.py b/webserver.py
index f2f85f596ab86e90f2676ec87167a7a4eaedef02..8171a5a93a08b45a3b265488e4c334778d0cc98b 100644
--- a/webserver.py
+++ b/webserver.py
@@ -1,99 +1,123 @@
-from http.server import SimpleHTTPRequestHandler, HTTPServer
-# import time
-import os
-import json
-from ohmpi import OhmPi
-# import threading
-import pandas as pd
-import shutil
-
-hostName = 'localhost'
-serverPort = 8080
-
-# https://gist.github.com/MichaelCurrie/19394abc19abd0de4473b595c0e37a3a
-
-with open('ohmpi_param.json') as json_file:
-    pardict = json.load(json_file)
-
-ohmpi = OhmPi(pardict)
-
-
-class MyServer(SimpleHTTPRequestHandler):
-    # because we use SimpleHTTPRequestHandler, we do not need to implement
-    # the do_GET() method (if we use the BaseHTTPRequestHandler, we would need to)
-   
-    # def do_GET(self):
-    #     # normal get for wepages (not so secure!)
-    #     print(self.command)
-    #     print(self.headers)
-    #     print(self.request)
-    #     self.send_response(200)
-    #     self.send_header("Content-type", "text/html")
-    #     self.end_headers()
-    #     with open(os.path.join('.', self.path[1:]), 'r') as f:
-    #         self.wfile.write(bytes(f.read(), "utf-8"))
-
-    def do_POST(self):
-        # global ohmpiThread, status, run
-
-        dic = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
-        rdic = {}
-        if dic['command'] == 'start':
-            ohmpi.measure()
-        elif dic['command'] == 'stop':
-            ohmpi.stop()
-        elif dic['command'] == 'getData':
-            # get all .csv file in data folder
-            fnames = os.listdir('data/')
-            ddic = {}
-            for fname in fnames:
-                if fname.replace('.csv', '') not in dic['surveyNames'] and fname != 'readme.txt':
-                    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['command'] == 'removeData':
-            shutil.rmtree('data')
-            os.mkdir('data')
-        elif dic['command'] == 'setConfig':
-            ohmpi.stop()
-            cdic = dic['config']
-            ohmpi.pardict['nb_electrodes'] = int(cdic['nbElectrodes'])
-            ohmpi.pardict['injection_duration'] = float(cdic['injectionDuration'])
-            ohmpi.pardict['nbr_meas'] = int(cdic['nbMeasurements'])
-            ohmpi.pardict['stack'] = int(cdic['nbStack'])
-            ohmpi.pardict['sequence_delay'] = int(cdic['sequenceDelay'])
-            print('setConfig', ohmpi.pardict)
-        elif dic['command'] == 'invert':
-            pass
-        elif dic['command'] == 'getResults':
-            pass
-        elif dic['command'] == 'download':
-            shutil.make_archive('data', 'zip', 'data')
-        else:
-            # command not found
-            rdic['response'] = 'command not found'
-        
-        rdic['status'] = ohmpi.status
-        self.send_response(200)
-        self.send_header('Content-Type', 'text/json')
-        self.end_headers()
-        self.wfile.write(bytes(json.dumps(rdic), 'utf8'))
-
-
-if __name__ == "__main__":        
-    webServer = HTTPServer((hostName, serverPort), MyServer)
-    print("Server started http://%s:%s" % (hostName, serverPort))
-
-    try:
-        webServer.serve_forever()
-    except KeyboardInterrupt:
-        pass
-
-    webServer.server_close()
-    print("Server stopped.")
+from http.server import SimpleHTTPRequestHandler, HTTPServer
+import time
+import os
+import json
+from ohmpi import OhmPi
+import threading
+import pandas as pd
+import shutil
+
+hostName = "raspberrypi.local" # works for AP-STA
+#hostName = "192.168.50.1"  # fixed IP in AP-STA mode
+#hostName = "0.0.0.0"  # for AP mode (not AP-STA)
+serverPort = 8080
+
+# https://gist.github.com/MichaelCurrie/19394abc19abd0de4473b595c0e37a3a
+
+with open('ohmpi_param.json') as json_file:
+    pardict = json.load(json_file)
+
+ohmpi = OhmPi(pardict, sequence='breadboard.txt')
+#ohmpi = OhmPi(pardict, sequence='dd16s0no8.txt')
+
+
+class MyServer(SimpleHTTPRequestHandler):
+    # because we use SimpleHTTPRequestHandler, we do not need to implement
+    # the do_GET() method (if we use the BaseHTTPRequestHandler, we would need to)
+   
+    # def do_GET(self):
+    #     # normal get for wepages (not so secure!)
+    #     print(self.command)
+    #     print(self.headers)
+    #     print(self.request)
+    #     self.send_response(200)
+    #     self.send_header("Content-type", "text/html")
+    #     self.end_headers()
+    #     with open(os.path.join('.', self.path[1:]), 'r') as f:
+    #         self.wfile.write(bytes(f.read(), "utf-8"))
+        
+    def do_POST(self):
+        global ohmpiThread, status, run
+        dic = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
+        rdic = {}
+        if dic['command'] == 'start':
+            ohmpi.measure()
+        elif dic['command'] == 'stop':
+            ohmpi.stop()
+        elif dic['command'] == '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.replace('.csv', '') not in dic['surveyNames'] 
+                    and fname != 'readme.txt'
+                    and '_rs' not in fname):
+                    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['command'] == 'removeData':
+            shutil.rmtree('data')
+            os.mkdir('data')
+        elif dic['command'] == 'setConfig':
+            ohmpi.stop()
+            cdic = dic['config']
+            ohmpi.pardict['nb_electrodes'] = int(cdic['nbElectrodes'])
+            ohmpi.pardict['injection_duration'] = float(cdic['injectionDuration'])
+            ohmpi.pardict['nbr_meas'] = int(cdic['nbMeasurements'])
+            ohmpi.pardict['nb_stack'] = int(cdic['nbStack'])
+            ohmpi.pardict['sequence_delay'] = int(cdic['sequenceDelay'])
+            if cdic['sequence'] != '':
+                with open('sequence.txt', 'w') as f:
+                    f.write(cdic['sequence'])
+                ohmpi.read_quad('sequence.txt')
+                print('new sequence set.')
+            print('setConfig', ohmpi.pardict)
+        elif dic['command'] == 'invert':
+            pass
+        elif dic['command'] == 'getResults':
+            pass
+        elif dic['command'] == 'rsCheck':
+            ohmpi.rs_check()
+            fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv'])
+            df = pd.read_csv('data/' + fnames[-1])
+            ddic = {
+                'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(),
+                'res': df['RS [kOhm]'].tolist()
+            }
+            rdic['data'] = ddic
+        elif dic['command'] == 'download':
+            shutil.make_archive('data', 'zip', 'data')
+        elif dic['command'] == 'shutdown':
+            print('shutting down...')
+            os.system('shutdown now -h')
+        elif dic['command'] == 'restart':
+            print('shutting down...')
+            os.system('reboot')
+        else:
+            # command not found
+            rdic['response'] = 'command not found'
+        
+        rdic['status'] = ohmpi.status
+        self.send_response(200)
+        self.send_header('Content-Type', 'text/json')
+        self.end_headers()
+        self.wfile.write(bytes(json.dumps(rdic), 'utf8'))
+
+
+if __name__ == "__main__":        
+    webServer = HTTPServer((hostName, serverPort), MyServer)
+    print("Server started http://%s:%s" % (hostName, serverPort))
+
+    try:
+        webServer.serve_forever()
+    except KeyboardInterrupt:
+        pass
+
+    webServer.server_close()
+    print("Server stopped.")