diff --git a/.gitignore b/.gitignore index 8c469515e45529e882055554f060660fb1f3babf..d660b3ade6bb036d44fd6f4b723765a414a1d6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ data/*.csv **/.ipynb_notebooks/** data.zip __pycache__ +Ohmpi_4elec_mqtt.py diff --git a/Ohmpi.py b/Ohmpi.py index 4f5e7f22f83637bff768f9cf20f48a8aa2aca02e..f6605abc7cd6c37ed70d698345c223e5fa3b6d9b 100644 --- a/Ohmpi.py +++ b/Ohmpi.py @@ -28,21 +28,27 @@ from datetime import datetime from termcolor import colored import threading -onpi = False # set to True if running on raspberrypi - -import board, busio,adafruit_tca9548a -import adafruit_ads1x15.ads1115 as ADS -from adafruit_ads1x15.analog_in import AnalogIn -from adafruit_mcp230xx.mcp23008 import MCP23008 -from adafruit_mcp230xx.mcp23017 import MCP23017 -import digitalio -from digitalio import Direction -from gpiozero import CPUTemperature +if False: + import board, busio, adafruit_tca9548a + import adafruit_ads1x15.ads1115 as ADS + from adafruit_ads1x15.analog_in import AnalogIn + from adafruit_mcp230xx.mcp23008 import MCP23008 + from adafruit_mcp230xx.mcp23017 import MCP23017 + import digitalio + from digitalio import Direction + from gpiozero import CPUTemperature current_time = datetime.now() print(current_time.strftime("%Y-%m-%d %H:%M:%S")) +# from logging_setup import setup_loggers +# from mqtt_setup import mqtt_client_setup +# msg_logger, msg_log_filename, data_logger, data_log_filename, logging_level = setup_loggers() +# mqtt_client, measurement_topic = mqtt_client_setup() +# msg_logger.info(f'publishing mqtt to topic {measurement_topic}') + + class OhmPi(object): def __init__(self, config=None, sequence=None, onpi=False, output='print'): """Create the main OhmPi object. @@ -73,15 +79,15 @@ class OhmPi(object): # default acquisition parameters self.pardict = { - injection_duration = 0.2 - nbr_meas = 100 - sequence_delay = 1 - nb_stack = 1 - export_path = 'data/measurement.csv' + 'injection_duration': 0.2, + 'nbr_meas': 100, + 'sequence_delay': 1, + 'nb_stack': 1, + 'export_path': 'data/measurement.csv' } # read in acquisition parameters - if config is None: + if config is not None: self._read_acquisition_parameters(config) self.dump('Initialized with configuration:' + str(self.pardict), level='debug') @@ -138,6 +144,7 @@ class OhmPi(object): elif self.output == 'mqtt': if level == 'debug': # TODO mqtt transmission here + pass def _read_acquisition_parameters(self, config): @@ -155,7 +162,7 @@ class OhmPi(object): config : str Path to the .json or dictionnary. """ - if isinstance(config, dic): + if isinstance(config, dict): self.pardict.update(config) else: dic = json.loads(config) @@ -166,56 +173,61 @@ class OhmPi(object): def _read_hardware_parameters(self): """Read hardware parameters from settings.py. """ - from settings.py import OHMPI_CONFIG - self.r_shunt = OHMPI_CONFIG['r_shunt'] # reference resistance value in ohm - self.Imax = OHMPI_CONFIG['Imax'] + from settings import OHMPI_CONFIG + self.id = OHMPI_CONFIG['id'] # ID of the OhmPi + self.r_shunt = OHMPI_CONFIG['R_shunt'] # reference resistance value in ohm + self.Imax = OHMPI_CONFIG['Imax'] # maximum current self.dump('The maximum current cannot be higher than 48 mA', level='warn') self.coef_p2 = OHMPI_CONFIG['coef_p2'] # slope for current conversion for ADS.P2, measurement in V/V self.coef_p3 = OHMPI_CONFIG['coef_p3'] # slope for current conversion for ADS.P3, measurement in V/V self.offset_p2 = OHMPI_CONFIG['offset_p2'] self.offset_p3 = OHMPI_CONFIG['offset_p3'] self.nb_samples = OHMPI_CONFIG['integer'] # number of samples measured for each stack - self.nb_elec = OHMPI_CONFIG['nb_elec'] # max number of electrodes (physically) + self.version = OHMPI_CONFIG['version'] # hardware version + self.max_elec = OHMPI_CONFIG['max_elec'] # maximum number of electrodes + self.dump('OHMPI_CONFIG = ' + str(OHMPI_CONFIG), level='debug') -def find_identical_in_line(self, quads): - """Find quadrupole which where A and B are identical. - If A and B are connected to the same relay, the Pi burns (short-circuit). - - Parameters - ---------- - quads : 1D or 2D array - List of quadrupoles of shape nquad x 4 or 1D vector of shape nquad. - - Returns - ------- - output : 1D array of int - List of index of rows where A and B are identical. - """ - # TODO is this needed for M and N? - - # if we have a 1D array (so only 1 quadrupole), make it 2D - if len(quads.shape) == 1: - quads = quads[None, :] - - output = np.where(quads[:, 0] == quads[:, 1])[0] - - # output = [] - # if array_object.ndim == 1: - # temp = np.zeros(4) - # for i in range(len(array_object)): - # temp[i] = np.count_nonzero(array_object == array_object[i]) - # if any(temp > 1): - # output.append(0) - # else: - # for i in range(len(array_object[:,1])): - # temp = np.zeros(len(array_object[1,:])) - # for j in range(len(array_object[1,:])): - # temp[j] = np.count_nonzero(array_object[i,:] == array_object[i,j]) - # if any(temp > 1): - # output.append(i) - return output - - def read_quad(self, filename): + + def find_identical_in_line(self, quads): + """Find quadrupole which where A and B are identical. + If A and B are connected to the same relay, the Pi burns (short-circuit). + + Parameters + ---------- + quads : 1D or 2D array + List of quadrupoles of shape nquad x 4 or 1D vector of shape nquad. + + Returns + ------- + output : 1D array of int + List of index of rows where A and B are identical. + """ + # TODO is this needed for M and N? + + # if we have a 1D array (so only 1 quadrupole), make it 2D + if len(quads.shape) == 1: + quads = quads[None, :] + + output = np.where(quads[:, 0] == quads[:, 1])[0] + + # output = [] + # if array_object.ndim == 1: + # temp = np.zeros(4) + # for i in range(len(array_object)): + # temp[i] = np.count_nonzero(array_object == array_object[i]) + # if any(temp > 1): + # output.append(0) + # else: + # for i in range(len(array_object[:,1])): + # temp = np.zeros(len(array_object[1,:])) + # for j in range(len(array_object[1,:])): + # temp[j] = np.count_nonzero(array_object[i,:] == array_object[i,j]) + # if any(temp > 1): + # output.append(i) + return output + + + def read_quad(self, filename): """Read quadrupole sequence from file. Parameters @@ -232,10 +244,10 @@ def find_identical_in_line(self, quads): output = np.loadtxt(filename, delimiter=" ", dtype=int) # load quadripole file # locate lines where the electrode index exceeds the maximum number of electrodes - test_index_elec = np.array(np.where(output > self.nb_elec)) + test_index_elec = np.array(np.where(output > self.max_elec)) # locate lines where electrode A == electrode B - test_same_elec = find_identical_in_line(output) + test_same_elec = self.find_identical_in_line(output) # if statement with exit cases (TODO rajouter un else if pour le deuxième cas du ticket #2) if test_index_elec.size != 0: @@ -320,7 +332,7 @@ def find_identical_in_line(self, quads): """ roles = ['A', 'B', 'M', 'N'] # another check to be sure A != B - if quadrupoles[0] != quadrupoles[1]: + if quadrupole[0] != quadrupole[1]: for i in range(0, 4): self.switch_mux(quadrupole[i], 'on', roles[i]) else: @@ -344,7 +356,7 @@ def find_identical_in_line(self, quads): """Switch off all multiplexer relays.""" roles = ['A', 'B', 'M', 'N'] for i in range(0, 4): - for j in range(1, self.nb_elec + 1): + for j in range(1, self.max_elec + 1): self.switch_mux(j, 'off', roles[i]) self.dump('All MUX switched off.', level='debug') @@ -400,9 +412,9 @@ def find_identical_in_line(self, quads): # sampling for each stack at the end of the injection meas = np.zero_like((3, self.nb_samples)) for k in range(0, self.nb_samples): - meas[0, k] = (AnalogIn(ads_current, ADS.P0).voltage*1000) / (50 * self.r_shunt) # reading current value on ADS channel A0 - meas[1, k] = AnalogIn(ads_voltage, ADS.P0).voltage * self.coefp2 * 1000 - meas[2, k] = AnalogIn(ads_voltage, ADS.P1).voltage * self.coefp3 * 1000 # reading voltage value on ADS channel A2 + meas[0, k] = (AnalogIn(self.ads_current, ADS.P0).voltage*1000) / (50 * self.r_shunt) # reading current value on ADS channel A0 + meas[1, k] = AnalogIn(self.ads_voltage, ADS.P0).voltage * self.coefp2 * 1000 + meas[2, k] = AnalogIn(self.ads_voltage, ADS.P1).voltage * self.coefp3 * 1000 # reading voltage value on ADS channel A2 # stop current injection pin1.value = False @@ -430,7 +442,7 @@ def find_identical_in_line(self, quads): time.sleep(2*(end_delay-start_delay)-(end_calc-start_delay)) # create dateframe and compute averaged values from all stacks - df = DataFrame({ + df = pd.DataFrame({ "time": [datetime.now()], "A": [(1)], "B": [(2)], @@ -444,7 +456,7 @@ def find_identical_in_line(self, quads): "nbStack": [nb_stack], "CPU temp [degC]": [CPUTemperature().temperature], "Time [s]": [(-start_time + time.time())], - "Nb samples [-]": [nb_samples] + "Nb samples [-]": [self.nb_samples] }) # round number to two decimal for nicer string output @@ -478,12 +490,16 @@ def find_identical_in_line(self, quads): # run the RS check self.dump('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? - def append_and_save(self, last_measurement): + def append_and_save(self, fname, last_measurement): """Append and save last measurement dataframe. Parameters @@ -492,13 +508,13 @@ def find_identical_in_line(self, quads): Last measurement taken in the form of a pandas dataframe. """ - if os.path.isfile(self.path): + if os.path.isfile(fname): # Load data file and append data to it - with open(self.path, 'a') as f: + with open(fname, 'a') as f: last_measurement.to_csv(f, header=False) else: # create data file and add headers - with open(self.path, 'a') as f: + with open(fname, 'a') as f: last_measurement.to_csv(f, header=True) @@ -533,15 +549,15 @@ def find_identical_in_line(self, quads): self.switch_mux_on(quad) # run a measurement - if onpi: - current_measurement = run_measurement(quad, self.pardict["stack"], self.pardict["injection_duration"]) + if self.onpi: + current_measurement = self.run_measurement(quad, self.pardict["stack"], self.pardict["injection_duration"]) else: # for testing, generate random data current_measurement = pd.DataFrame({ '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)) + self.switch_mux_off(quad) # save data and print in a text file self.append_and_save(fname, current_measurement) @@ -575,5 +591,6 @@ def find_identical_in_line(self, quads): # test #with open('ohmpi_param.json') as json_file: # pardict = json.load(json_file) -#ohmpi = OhmPi(pardict) +ohmpi = OhmPi() #ohmpi.measure() + diff --git a/requirements.txt b/requirements.txt index cbb65fcd99fbe729896ee728246880de69bf8c85..76ed151b75175e25781bdeccfb6f2f02a0a376e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ adafruit-circuitpython-tca9548a adafruit-circuitpython-mcp230xx gpiozero termcolor +board diff --git a/settings.py b/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..d57801920eeae972dd231ced847190b7de227c81 --- /dev/null +++ b/settings.py @@ -0,0 +1,41 @@ +# OhmPi configuration +OHMPI_CONFIG = { + 'id': '0001', # Unique identifier of the OhmPi board (string) + 'R_shunt': 2, # Shunt resistance in Ohms + 'Imax': 4800/50/2, # Maximum current + 'coef_p2': 2.50, # slope for current conversion for ADS.P2, measurement in V/V + 'coef_p3': 2.50, # slope for current conversion for ADS.P3, measurement in V/V + 'offset_p2': 0, + 'offset_p3': 0, + 'integer': 2, # Max value 10 WHAT IS THIS? + 'version': 2, + 'max_elec': 64, +} + +# local messages logging configuration +LOGGING_CONFIG = { + 'debug_mode': False, + 'logging_to_console': False, + 'file_name': 'ohmpi_log', + 'max_bytes': 262144, + 'backup_count': 30, + 'when': 'd', + 'interval': 1 +} + +# local data logging configuration +DATA_LOGGING_CONFIG = { + 'file_name': 'data_log', + 'max_bytes': 16777216, + 'backup_count': 1024, + 'when': 'd', + 'interval': 1 +} + +# MQTT configuration parameters +MQTT_CONFIG = { + 'mqtt_broker': 'mg3d-dev.umons.ac.be', + 'client_id': f'ohmpi_sn_{OHMPI_CONFIG["id"]}', + 'control_topic': f'cmd_ohmpi_sn_{OHMPI_CONFIG["id"]}', + 'measurements_topic': f'measurements_ohmpi_sn_{OHMPI_CONFIG["id"]}' +}