diff --git a/http_interface.py b/http_interface.py index 4a42e878f005ae49f1a332001b3859f800bc0b36..6b8e85d6243c1d27144c5b1c81fb4686a130d524 100644 --- a/http_interface.py +++ b/http_interface.py @@ -92,19 +92,25 @@ class MyServer(SimpleHTTPRequestHandler): dic = json.loads(self.rfile.read(int(self.headers['Content-Length']))) rdic = {} # response dictionary if dic['cmd'] == 'run_sequence': - payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_sequence'}) + 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': + print(dic) # get all .csv file in data folder - fnames = [fname for fname in os.listdir('data/') if fname[-4:] == '.csv'] + fnames = sorted([fname for fname in os.listdir('data/') if fname[-4:] == '.csv']) ddic = {} + fdownloaded = True + if dic['lastSurvey'] == '0': + fdownloaded = False for fname in fnames: - if (fname.replace('.csv', '') not in dic['surveyNames'] - and fname != 'readme.txt' - and '_rs' not in fname): + if (((fname != 'readme.txt') + and ('_rs' not in fname)) + and ((fname.replace('.csv', '') == dic['lastSurvey']) + or (fdownloaded == False))): + fdownloaded = False df = pd.read_csv('data/' + fname) ddic[fname.replace('.csv', '')] = { 'a': df['A'].tolist(), diff --git a/index.html b/index.html index db104d009f47532bc3ed3fcbdd304433b72c4496..f16bc60e9bc9e468517682e7c3ab20167ff7af0d 100644 --- a/index.html +++ b/index.html @@ -34,8 +34,8 @@ <!-- 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> <div class="mb3 row"> @@ -163,9 +163,9 @@ // run button function runBtnFunc() { - sendCommand('{"cmd": "run_sequence"}', function(x) { - console.log(x['status']) - if (x['status'] == 'running') { + sendCommand('{"cmd": "run_multiple_sequences"}', function(x) { + console.log(x['ohmpi_status']) + if (x['ohmpi_status'] == 'running') { output.innerHTML = 'Status: measuring...' } }) @@ -176,7 +176,7 @@ // interrupt button function stopBtnFunc() { sendCommand('{"cmd": "interrupt"}', function(x) { - output.innerHTML = 'Status: ' + x['status'] + output.innerHTML = 'Status: ' + x['ohmpi_status'] clearInterval(interv) getData() }) @@ -375,13 +375,17 @@ // getData function getData() { + let lastSurvey = '0' + if (Object.keys(data).length > 1) { + lastSurvey = Object.keys(data).slice(-2, -1) + } sendCommand(JSON.stringify({ 'cmd': 'getData', - 'surveyNames': Object.keys(data).slice(0, -1) + 'lastSurvey': lastSurvey // last survey is often partial so we download it again }), function(ddic) { // update status - output.innerHTML = 'Status: ' + ddic['status'] + //output.innerHTML = 'Status: ' + ddic['status'] // update data dic with new data data = { // destructuring assignement (magic! :o) @@ -499,7 +503,7 @@ function removeDataBtnFunc() { sendCommand('{"cmd": "removeData"}',function(x) { data = {} - output.innerHTML = 'Status: ' + x['status'] + ' (all data cleared)' + output.innerHTML = 'Status: ' + x['ohmpi_status'] + ' (all data cleared)' console.log('all data removed') }) } diff --git a/ohmpi.py b/ohmpi.py index a3577a507da4465590d3285a47c9b92864d4083b..7b2a6a5d0a0017f51fffd141ac7b4cd1937f4e76 100644 --- a/ohmpi.py +++ b/ohmpi.py @@ -380,7 +380,7 @@ class OhmPi(object): else: mcp2.get_pin(relay_nr - 1).value = False - self.exec_logger.debug(f'Switching relay {relay_nr} {state} for electrode {electrode_nr}') + self.exec_logger.debug(f'Switching relay {relay_nr} ({str(hex(self.board_addresses[role]))}) {state} for electrode {electrode_nr}') else: self.exec_logger.warning(f'Unable to address electrode nr {electrode_nr}') @@ -428,11 +428,13 @@ class OhmPi(object): Parameters ---------- - channel: + channel : object + Instance of ADS where voltage is measured. Returns ------- - float + gain : float + Gain to be applied on ADS1115. """ gain = 2 / 3 if (abs(channel.voltage) < 2.040) and (abs(channel.voltage) >= 1.023): @@ -443,7 +445,7 @@ class OhmPi(object): gain = 8 elif abs(channel.voltage) < 0.256: gain = 16 - self.exec_logger.debug(f'Setting gain to {gain}') + #self.exec_logger.debug(f'Setting gain to {gain}') return gain def _compute_tx_volt(self, best_tx_injtime=0.1, strategy='vmax', tx_volt=5): @@ -587,7 +589,8 @@ class OhmPi(object): def run_measurement(self, quad=None, nb_stack=None, injection_duration=None, - autogain=True, strategy='constant', tx_volt=5, best_tx_injtime=0.1): + autogain=True, strategy='constant', tx_volt=5, best_tx_injtime=0.1, + cmd_id=None): """Do a 4 electrode measurement and measure transfer resistance obtained. Parameters @@ -711,7 +714,7 @@ class OhmPi(object): else: self.pin0.value = False self.pin1.value = True # current injection nr2 - self.exec_logger.debug(str(n) + ' ' + str(self.pin0.value) + ' ' + str(self.pin1.value)) + self.exec_logger.debug('Stack ' + str(n) + ' ' + str(self.pin0.value) + ' ' + str(self.pin1.value)) # measurement of current i and voltage u during injection meas = np.zeros((self.nb_samples, 3)) * np.nan @@ -842,17 +845,6 @@ class OhmPi(object): d = {'time': datetime.now().isoformat(), 'A': quad[0], 'B': quad[1], 'M': quad[2], 'N': quad[3], 'R [ohm]': np.abs(np.random.randn(1)).tolist()} - # round number to two decimal for nicer string output - output = [f'{k}\t' for k in d.keys()] - output = str(output)[:-1] + '\n' - for k in d.keys(): - if isinstance(d[k], float): - val = np.round(d[k], 2) - else: - val = d[k] - output += f'{val}\t' - output = output[:-1] - # to the data logger dd = d.copy() dd.pop('fulldata') # too much for logger @@ -860,7 +852,14 @@ class OhmPi(object): dd.update({'B': str(dd['B'])}) dd.update({'M': str(dd['M'])}) dd.update({'N': str(dd['N'])}) - self.data_logger.info(json.dumps(dd)) + + # round float to 2 decimal + for key in dd.keys(): + if isinstance(dd[key], float): + dd[key] = np.round(dd[key], 3) + + dd['cmd_id'] = str(cmd_id) + self.data_logger.info(dd) return d @@ -891,49 +890,47 @@ class OhmPi(object): # self.run = True self.status = 'running' - if self.on_pi: - # make sure all mux are off to start with - self.reset_mux() + # 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.switch_mux_on(quad) # put before raising the pins (otherwise conflict i2c) - d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=1, tx_volt=tx_volt, autogain=False) + # measure all quad of the RS sequence + for i in range(0, quads.shape[0]): + quad = quads[i, :] # quadrupole + self.switch_mux_on(quad) # put before raising the pins (otherwise conflict i2c) + d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=0.25, tx_volt=tx_volt, autogain=False) - if self.idps: - voltage = tx_volt * 1000. # imposed voltage on dps5005 - else: - voltage = d['Vmn [mV]'] - current = d['I [mA]'] - - # compute resistance measured (= contact resistance) - resist = abs(voltage / current) /1000. - #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.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) - self.reset_mux() + if self.idps: + voltage = tx_volt * 1000. # imposed voltage on dps5005 + else: + voltage = d['Vmn [mV]'] + current = d['I [mA]'] + + # compute resistance measured (= contact resistance) + resist = abs(voltage / current) /1000. + #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.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) else: pass self.status = 'idle' @@ -1016,6 +1013,11 @@ class OhmPi(object): while not self.status == 'idle': time.sleep(0.1) status = True + elif cmd == 'run_multiple_sequences': + self.run_multiple_sequences(cmd_id) + while not self.status == 'idle': + time.sleep(0.1) + status = True elif cmd == 'interrupt': self.interrupt() status = True @@ -1039,10 +1041,9 @@ class OhmPi(object): self.exec_logger.warning(f'Unable to decode command {command}: {e}') status = False finally: - reply = {'cmd_id': cmd_id, 'status': status} + reply = {'cmd_id': cmd_id, 'status':status, 'ohmpi_status': self.status} reply = json.dumps(reply) self.exec_logger.debug(f'Execution report: {reply}') - def set_sequence(self, args): try: @@ -1052,15 +1053,13 @@ class OhmPi(object): self.exec_logger.warning(f'Unable to set sequence: {e}') status = False - def run_sequence(self, cmd_id=None, **kwargs): + def run_sequence(self, **kwargs): """Run sequence synchronously (=blocking on main thread). Additional arguments are passed to run_measurement(). """ self.status = 'running' self.exec_logger.debug(f'Status: {self.status}') self.exec_logger.debug(f'Measuring sequence: {self.sequence}') - - t0 = time.time() # create filename with timestamp filename = self.settings["export_path"].replace('.csv', @@ -1087,173 +1086,62 @@ class OhmPi(object): self.switch_mux_on(quad) # run a measurement - if self.on_pi: - acquired_data = self.run_measurement(quad, **kwargs) - else: # for testing, generate random data - acquired_data = { - 'A': [quad[0]], 'B': [quad[1]], 'M': [quad[2]], 'N': [quad[3]], - 'R [ohm]': np.abs(np.random.randn(1)) - } - + acquired_data = self.run_measurement(quad, **kwargs) + # switch mux off self.switch_mux_off(quad) - # add command_id in dataset - acquired_data.update({'cmd_id': cmd_id}) - # log data to the data logger - self.data_logger.info(f'{acquired_data}') - print(f'{acquired_data}') # save data and print in a text file self.append_and_save(filename, acquired_data) self.exec_logger.debug(f'{i+1:d}/{n:d}') self.status = 'idle' - def run_sequence_async(self, cmd_id=None, **kwargs): + def run_sequence_async(self, **kwargs): """ Run the sequence in a separate thread. Can be stopped by 'OhmPi.interrupt()'. Additional arguments are passed to run_measurement(). """ - # self.run = True - self.status = 'running' - self.exec_logger.debug(f'Status: {self.status}') - self.exec_logger.debug(f'Measuring sequence: {self.sequence}') - def func(): - # if self.status != 'running': - # self.exec_logger.warning('Data acquisition interrupted') - # break - t0 = time.time() - - # create filename with timestamp - filename = self.settings["export_path"].replace('.csv', - f'_{datetime.now().strftime("%Y%m%dT%H%M%S")}.csv') - self.exec_logger.debug(f'Saving to {filename}') - - # make sure all multiplexer are off - self.reset_mux() - - # measure all quadrupole of the sequence - if self.sequence is None: - n = 1 - else: - n = self.sequence.shape[0] - for i in range(0, n): - if self.sequence is None: - quad = np.array([0, 0, 0, 0]) - else: - quad = self.sequence[i, :] # quadrupole - if self.status == 'stopping': - break - - # call the switch_mux function to switch to the right electrodes - self.switch_mux_on(quad) - - # run a measurement - if self.on_pi: - acquired_data = self.run_measurement(quad, **kwargs) - else: # for testing, generate random data - acquired_data = { - 'A': [quad[0]], 'B': [quad[1]], 'M': [quad[2]], 'N': [quad[3]], - 'R [ohm]': np.abs(np.random.randn(1)) - } - - # switch mux off - self.switch_mux_off(quad) - - # add command_id in dataset - acquired_data.update({'cmd_id': cmd_id}) - # log data to the data logger - self.data_logger.info(f'{acquired_data}') - print(f'{acquired_data}') - # save data and print in a text file - self.append_and_save(filename, acquired_data) - self.exec_logger.debug(f'{i+1:d}/{n:d}') - - self.status = 'idle' - + self.run_sequence(**kwargs) + self.thread = threading.Thread(target=func) self.thread.start() + self.status = 'idle' def measure(self, *args, **kwargs): warnings.warn('This function is deprecated. Use run_multiple_sequences() instead.', DeprecationWarning) - self.run_sequence(self, *args, **kwargs) + self.run_multiple_sequences(self, *args, **kwargs) - def run_multiple_sequences(self, cmd_id=None, **kwargs): + def run_multiple_sequences(self, sequence_delay=None, **kwargs): """ Run multiple sequences in a separate thread for monitoring mode. Can be stopped by 'OhmPi.interrupt()'. Additional arguments are passed to run_measurement(). + + Parameters + ---------- + sequence_delay : int, optional + Number of seconds at which the sequence must be started from each others. + kwargs : dict, optional + See help(k.run_measurement) for more info. """ # self.run = True + if sequence_delay == None: + sequence_delay = self.settings['sequence_delay'] self.status = 'running' - self.exec_logger.debug(f'Status: {self.status}') - self.exec_logger.debug(f'Measuring sequence: {self.sequence}') - def func(): for g in range(0, self.settings["nb_meas"]): # for time-lapse monitoring if self.status != 'running': self.exec_logger.warning('Data acquisition interrupted') break t0 = time.time() - - # create filename with timestamp - filename = self.settings["export_path"].replace('.csv', - f'_{datetime.now().strftime("%Y%m%dT%H%M%S")}.csv') - self.exec_logger.debug(f'Saving to {filename}') - - # make sure all multiplexer are off - self.reset_mux() - - # measure all quadrupole of the sequence - if self.sequence is None: - n = 1 - else: - n = self.sequence.shape[0] - for i in range(0, n): - if self.sequence is None: - quad = np.array([0, 0, 0, 0]) - else: - quad = self.sequence[i, :] # quadrupole - if self.status == 'stopping': - break - - # call the switch_mux function to switch to the right electrodes - self.switch_mux_on(quad) - - # run a measurement - if self.on_pi: - acquired_data = self.run_measurement(quad, **kwargs) - else: # for testing, generate random data - acquired_data = { - 'A': [quad[0]], 'B': [quad[1]], 'M': [quad[2]], 'N': [quad[3]], - 'R [ohm]': np.abs(np.random.randn(1)) - } - - # switch mux off - self.switch_mux_off(quad) - - # add command_id in dataset - acquired_data.update({'cmd_id': cmd_id}) - # log data to the data logger - self.data_logger.info(f'{acquired_data}') - print(f'{acquired_data}') - # save data and print in a text file - self.append_and_save(filename, acquired_data) - self.exec_logger.debug(f'{i+1:d}/{n:d}') - - # compute time needed to take measurement and subtract it from interval - # between two sequence run (= sequence_delay) - measuring_time = time.time() - t0 - sleep_time = self.settings["sequence_delay"] - measuring_time - - if sleep_time < 0: - # it means that the measuring time took longer than the sequence delay - sleep_time = 0 - self.exec_logger.warning('The measuring time is longer than the sequence delay. ' - 'Increase the sequence delay') + self.run_sequence(**kwargs) # sleeping time between sequence + dt = sequence_delay - (time.time() - t0) + if dt < 0: + dt = 0 if self.settings["nb_meas"] > 1: - time.sleep(sleep_time) # waiting for next measurement (time-lapse) + time.sleep(dt) # waiting for next measurement (time-lapse) self.status = 'idle' self.thread = threading.Thread(target=func) diff --git a/test_ohmpi_mux.py b/test_ohmpi_mux.py index cf09c17eac674d056349699993058cb89a472f44..ed3222f6b4cd2e79c282d71f9e025f1f57455181 100644 --- a/test_ohmpi_mux.py +++ b/test_ohmpi_mux.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd import os +import time # configure testing idps = False @@ -102,12 +103,13 @@ if use_mux: # run sequence asynchronously and save data to file k.run_sequence_async(nb_stack=1, injection_duration=0.25) - time.sleep(2) + time.sleep(2) # if too short, it will still be resetting the mux (= no effect) k.interrupt() # will kill the asynchronous sequence running # run a series of asynchronous sequences + k.settings['nb_meas'] = 2 # run the sequence twice k.run_multiple_sequences(nb_stack=1, injection_duration=0.25) - time.sleep(2) + time.sleep(5) k.interrupt()