diff --git a/configs/config_mb_2023_3_mux_2024.py b/configs/config_mb_2023_3_mux_2024.py
index 2a31c569ab35a3d81eb08a21c86c5fe404da1f89..c52db04a16c491950e2492320c4d6add89c08851 100644
--- a/configs/config_mb_2023_3_mux_2024.py
+++ b/configs/config_mb_2023_3_mux_2024.py
@@ -18,7 +18,7 @@ OHMPI_CONFIG = {
 }
 
 HARDWARE_CONFIG = {
-    'ctl': {'model': 'raspberry_pi_i2c'},
+    'ctl': {'model': 'raspberry_pi', 'connection': 'i2c'},
     'pwr': {'model': 'pwr_batt', 'voltage': 12.},
     'tx':  {'model': 'ohmpi_card_3_15',
              'mcp_board_address': 0x20,
diff --git a/configs/config_mb_2023_mux_2024_2_roles_MN.py b/configs/config_mb_2023_mux_2024_2_roles_MN.py
index 7ca25decc0b0785d4b7efd4705de792757ca4385..04057da5adcb12f84069eafb666be5df45c97b77 100644
--- a/configs/config_mb_2023_mux_2024_2_roles_MN.py
+++ b/configs/config_mb_2023_mux_2024_2_roles_MN.py
@@ -40,9 +40,9 @@ HARDWARE_CONFIG = {
                      {'model' : 'mux_2024_rev_0_0', # 'ohmpi_i2c_mux64_v1.01',
                       'tca_address': None,
                       'tca_channel': 0,
-                      'mcp_0' : '0x22',  # TODO : Replace this with pos of jumper on MUX board (address doesn't mean anything for the average user...
-                      'mcp_1' : '0x23',  # TODO : Replace this with pos of jumper on MUX board (address doesn't mean anything for the average user...)
-                      'roles' : {'M': 'X', 'N': 'Y'},
+                      'mcp_0': '0x22',  # TODO : Replace this with pos of jumper on MUX board (address doesn't mean anything for the average user...
+                      'mcp_1': '0x23',  # TODO : Replace this with pos of jumper on MUX board (address doesn't mean anything for the average user...)
+                      'roles': {'M': 'X', 'N': 'Y'},
                       'voltage_max': 12.
                 }},
             'default': {'voltage_max': 100., 'current_max': 3.}}
diff --git a/configs/config_mb_2024_rev_0_2.py b/configs/config_mb_2024_rev_0_2.py
new file mode 100644
index 0000000000000000000000000000000000000000..188837744ebf5d5dae9e304bfe79b48cb5b69765
--- /dev/null
+++ b/configs/config_mb_2024_rev_0_2.py
@@ -0,0 +1,106 @@
+import logging
+from ohmpi.utils import get_platform
+
+from paho.mqtt.client import MQTTv31
+
+_, on_pi = get_platform()
+# DEFINE THE ID OF YOUR OhmPi
+ohmpi_id = '0001' if on_pi else 'XXXX'
+# DEFINE YOUR MQTT BROKER (DEFAULT: 'localhost')
+mqtt_broker = 'localhost' if on_pi else 'NAME_YOUR_BROKER_WHEN_IN_SIMULATION_MODE_HERE'
+# DEFINE THE SUFFIX TO ADD TO YOUR LOGS FILES
+logging_suffix = ''
+
+# OhmPi configuration
+OHMPI_CONFIG = {
+    'id': ohmpi_id,  # Unique identifier of the OhmPi board (string)
+    'settings': 'ohmpi_settings.json',  # INSERT YOUR FAVORITE SETTINGS FILE HERE
+}
+
+HARDWARE_CONFIG = {
+    'ctl': {'model': 'raspberry_pi_i2c'},
+    'pwr': {'model': 'pwr_batt', 'voltage': 12.},
+    'tx':  {'model': 'mb_2024_rev_0_2',
+             'voltage_max': 50.,  # Maximum voltage supported by the TX board [V]
+             'current_max': 4800 / 50 / 2,  # Maximum current supported by the TX board [mA]
+             'r_shunt': 2  # Shunt resistance in Ohms
+            },
+    'rx':  {'model': 'mb_2024_rev_0_2',
+             'coef_p2': 2.50,  # slope for conversion for ADS, measurement in V/V
+             'latency': 0.010,  # latency in seconds in continuous mode
+             'sampling_rate': 50  # number of samples per second
+            }
+}
+
+# SET THE LOGGING LEVELS, MQTT BROKERS AND MQTT OPTIONS ACCORDING TO YOUR NEEDS
+# Execution logging configuration
+EXEC_LOGGING_CONFIG = {
+    'logging_level': logging.INFO,
+    'log_file_logging_level': logging.DEBUG,
+    'logging_to_console': True,
+    'file_name': f'exec{logging_suffix}.log',
+    'max_bytes': 262144,
+    'backup_count': 30,
+    'when': 'd',
+    'interval': 1
+}
+
+# Data logging configuration
+DATA_LOGGING_CONFIG = {
+    'logging_level': logging.INFO,
+    'logging_to_console': True,
+    'file_name': f'data{logging_suffix}.log',
+    'max_bytes': 16777216,
+    'backup_count': 1024,
+    'when': 'd',
+    'interval': 1
+}
+
+# State of Health logging configuration (For a future release)
+SOH_LOGGING_CONFIG = {
+    'logging_level': logging.INFO,
+    'logging_to_console': True,
+    'log_file_logging_level': logging.DEBUG,
+    'file_name': f'soh{logging_suffix}.log',
+    'max_bytes': 16777216,
+    'backup_count': 1024,
+    'when': 'd',
+    'interval': 1
+}
+
+# MQTT logging configuration parameters
+MQTT_LOGGING_CONFIG = {
+    'hostname': mqtt_broker,
+    'port': 1883,
+    'qos': 2,
+    'retain': False,
+    'keepalive': 60,
+    'will': None,
+    'auth': {'username': 'mqtt_user', 'password': 'mqtt_password'},
+    'tls': None,
+    'protocol': MQTTv31,
+    'transport': 'tcp',
+    'client_id': f'{OHMPI_CONFIG["id"]}',
+    'exec_topic': f'ohmpi_{OHMPI_CONFIG["id"]}/exec',
+    'exec_logging_level': logging.DEBUG,
+    'data_topic': f'ohmpi_{OHMPI_CONFIG["id"]}/data',
+    'data_logging_level': DATA_LOGGING_CONFIG['logging_level'],
+    'soh_topic': f'ohmpi_{OHMPI_CONFIG["id"]}/soh',
+    'soh_logging_level': SOH_LOGGING_CONFIG['logging_level']
+}
+
+# MQTT control configuration parameters
+MQTT_CONTROL_CONFIG = {
+    'hostname': mqtt_broker,
+    'port': 1883,
+    'qos': 2,
+    'retain': False,
+    'keepalive': 60,
+    'will': None,
+    'auth': {'username': 'mqtt_user', 'password': 'mqtt_password'},
+    'tls': None,
+    'protocol': MQTTv31,
+    'transport': 'tcp',
+    'client_id': f'{OHMPI_CONFIG["id"]}',
+    'ctrl_topic': f'ohmpi_{OHMPI_CONFIG["id"]}/ctrl'
+}
diff --git a/ohmpi/hardware_components/abstract_hardware_components.py b/ohmpi/hardware_components/abstract_hardware_components.py
index 977736fd22c8ff69262a0a637588be66bc645ee6..beaa41c17077551cd21a6b9686836534290da0f6 100644
--- a/ohmpi/hardware_components/abstract_hardware_components.py
+++ b/ohmpi/hardware_components/abstract_hardware_components.py
@@ -9,7 +9,7 @@ from threading import Event, Barrier, BrokenBarrierError
 class CtlAbstract(ABC):
     def __init__(self, **kwargs):
         self.board_name = kwargs.pop('board_name', 'unknown CTL hardware')
-        self.bus = None  # TODO: allow for several buses
+        self.connections = None  # TODO: allow for several buses
         self.exec_logger = kwargs.pop('exec_logger', None)
         if self.exec_logger is None:
             self.exec_logger = create_stdout_logger('exec_ctl')
@@ -55,7 +55,7 @@ class PwrAbstract(ABC):
         self._current_max = kwargs.pop('current_max', 0.)
         self._voltage_min = kwargs.pop('voltage_min', 0.)
         self._voltage_max = kwargs.pop('voltage_max', 0.)
-        self.ctl = kwargs.pop('ctl', None)
+        self.io = kwargs.pop('io', None)
 
     @property
     @abstractmethod
@@ -110,7 +110,7 @@ class MuxAbstract(ABC):
         if self.board_id is None:
             self.exec_logger.error(f'MUX {self.board_name} should have an id !')
         self.exec_logger.debug(f'MUX {self.board_id} ({self.board_name}) initialization')
-        self.ctl = kwargs.pop('ctl', None)
+        self.io = kwargs.pop('io', None)
         cabling = kwargs.pop('cabling', None)
         self.cabling = {}
         if cabling is not None:
@@ -254,7 +254,7 @@ class TxAbstract(ABC):
         self.soh_logger = kwargs.pop('soh_logger', None)
         if self.soh_logger is None:
             self.soh_logger = create_stdout_logger('soh_tx')
-        self.ctl = kwargs.pop('ctl', None)
+        self.io = kwargs.pop('io', None)
         self.pwr = kwargs.pop('pwr', None)
         self._polarity = 0
         self._injection_duration = None
@@ -371,7 +371,7 @@ class RxAbstract(ABC):
         self.soh_logger = kwargs.pop('soh_logger', None)
         if self.soh_logger is None:
             self.soh_logger = create_stdout_logger('soh_rx')
-        self.ctl = kwargs.pop('ctl', None)
+        self.io = kwargs.pop('io', None)
         self.board_name = kwargs.pop('board_name', 'unknown RX hardware')
         self._sampling_rate = kwargs.pop('sampling_rate', 1)  # ms
         self.exec_logger.debug(f'{self.board_name} RX initialization')
diff --git a/ohmpi/hardware_components/mb_2024_rev_0_0.py b/ohmpi/hardware_components/mb_2024_0_2.py
similarity index 52%
rename from ohmpi/hardware_components/mb_2024_rev_0_0.py
rename to ohmpi/hardware_components/mb_2024_0_2.py
index 3b2f9778323108ea9fb972601843d1403544aa9a..20840a9f83364312ca799734f0a3f2593d9a1f63 100644
--- a/ohmpi/hardware_components/mb_2024_rev_0_0.py
+++ b/ohmpi/hardware_components/mb_2024_0_2.py
@@ -1,7 +1,9 @@
+import datetime
 import importlib
 from ohmpi.config import HARDWARE_CONFIG
 import adafruit_ads1x15.ads1115 as ads  # noqa
 from adafruit_ads1x15.analog_in import AnalogIn  # noqa
+from adafruit_ads1x15.ads1x15 import Mode  # noqa
 from adafruit_mcp230xx.mcp23008 import MCP23008  # noqa
 from digitalio import Direction  # noqa
 import minimalmodbus  # noqa
@@ -9,7 +11,8 @@ import time
 import numpy as np
 import os
 from ohmpi.hardware_components import TxAbstract, RxAbstract
-ctl_module = importlib.import_module(f'ohmpi.hardware_components.{HARDWARE_CONFIG["hardware"]["ctl"]["model"]}')
+ctl_name = HARDWARE_CONFIG['ctl'].pop('board_name', 'raspberry_pi_i2c')
+ctl_module = importlib.import_module(f'ohmpi.hardware_components.{ctl_name}')
 
 TX_CONFIG = HARDWARE_CONFIG['tx']
 RX_CONFIG = HARDWARE_CONFIG['rx']
@@ -19,27 +22,40 @@ RX_CONFIG = HARDWARE_CONFIG['rx']
 # ADC for voltage
 voltage_adc_voltage_min = 10.  # mV
 voltage_adc_voltage_max = 4500.  # mV
-
+sampling_rate = 20.  # Hz
+data_rate = 860.  # S/s?
 RX_CONFIG['voltage_min'] = np.min([voltage_adc_voltage_min, RX_CONFIG.pop('voltage_min', np.inf)])  # mV
 RX_CONFIG['voltage_max'] = np.min([voltage_adc_voltage_max, RX_CONFIG.pop('voltage_max', np.inf)])  # mV
+RX_CONFIG['sampling_rate'] = RX_CONFIG.pop('sampling_rate', sampling_rate)
+RX_CONFIG['data_rate'] = RX_CONFIG.pop('data_rate', data_rate)
+# RX_CONFIG['coef_p2'] = RX_CONFIG.pop('coef_p2', 2.5)
+RX_CONFIG['latency'] = RX_CONFIG.pop('latency', 0.01)
+RX_CONFIG['bias'] = RX_CONFIG.pop('bias', 0.)
+
 
 # *** TX ***
 # ADC for current
 current_adc_voltage_min = 10.  # mV
-current_adc_voltage_max = 4500. # mV
-
-# DPS
-dps_voltage_max = 50.  # V
-dps_default_voltage = 5.  # V
-dps_switch_on_warmup = 4.  # seconds
-tx_low_battery = 12. # V
-
-TX_CONFIG['current_min'] = np.min([current_adc_voltage_min / (TX_CONFIG['r_shunt'] * 50), TX_CONFIG.pop('current_min', np.inf)])  # mA
-TX_CONFIG['current_max'] = np.min([current_adc_voltage_max / (TX_CONFIG['r_shunt'] * 50), TX_CONFIG.pop('current_max', np.inf)])  # mA
-TX_CONFIG['voltage_max'] = np.min([dps_voltage_max, TX_CONFIG.pop('voltage_max', np.inf)])  # V
-TX_CONFIG['default_voltage'] = np.min([TX_CONFIG.pop('default_voltage', dps_default_voltage), TX_CONFIG['voltage_max']])  # V
-TX_CONFIG['dps_switch_on_warm_up'] = TX_CONFIG.pop('dps_switch_on_warmup', dps_switch_on_warmup)
-TX_CONFIG['low_battery'] = TX_CONFIG.pop('low_battery', tx_low_battery)
+current_adc_voltage_max = 4500.  # mV
+low_battery = 12.  # V (conventional value as it is not measured on this board)
+tx_mcp_board_address = 0x21  #
+# pwr_voltage_max = 12.  # V
+# pwr_default_voltage = 12.  # V
+# pwr_switch_on_warmup = 0.  # seconds
+
+TX_CONFIG['current_min'] = np.min([current_adc_voltage_min / (TX_CONFIG['r_shunt'] * 50),
+                                   TX_CONFIG.pop('current_min', np.inf)])  # mA
+TX_CONFIG['current_max'] = np.min([current_adc_voltage_max / (TX_CONFIG['r_shunt'] * 50),
+                                   TX_CONFIG.pop('current_max', np.inf)])  # mA
+# TX_CONFIG['voltage_max'] = np.min([pwr_voltage_max, TX_CONFIG.pop('voltage_max', np.inf)])  # V
+TX_CONFIG['voltage_max'] = TX_CONFIG.pop('voltage_max', np.inf)  # V
+TX_CONFIG['voltage_min'] = -TX_CONFIG['voltage_max']  # V
+TX_CONFIG['default_voltage'] = np.min([TX_CONFIG.pop('default_voltage', np.inf), TX_CONFIG['voltage_max']])  # V
+# TX_CONFIG['pwr_switch_on_warm_up'] = TX_CONFIG.pop('pwr_switch_on_warmup', pwr_switch_on_warmup)
+TX_CONFIG['mcp_board_address'] = TX_CONFIG.pop('mcp_board_address', tx_mcp_board_address)
+TX_CONFIG['low_battery'] = TX_CONFIG.pop('low_battery', low_battery)
+TX_CONFIG['latency'] = TX_CONFIG.pop('latency', 0.01)
+TX_CONFIG['bias'] = TX_CONFIG.pop('bias', 0.)
 
 
 def _gain_auto(channel):
@@ -57,31 +73,37 @@ def _gain_auto(channel):
     """
 
     gain = 2 / 3
-    if (abs(channel.voltage) < 2.040) and (abs(channel.voltage) >= 1.0):
+    if (abs(channel.voltage) < 2.048) and (abs(channel.voltage) >= 1.024):
         gain = 2
-    elif (abs(channel.voltage) < 1.0) and (abs(channel.voltage) >= 0.500):
+    elif (abs(channel.voltage) < 1.024) and (abs(channel.voltage) >= 0.512):
         gain = 4
-    elif (abs(channel.voltage) < 0.500) and (abs(channel.voltage) >= 0.250):
+    elif (abs(channel.voltage) < 0.512) and (abs(channel.voltage) >= 0.256):
         gain = 8
-    elif abs(channel.voltage) < 0.250:
+    elif abs(channel.voltage) < 0.256:
         gain = 16
     return gain
 
+
 class Tx(TxAbstract):
     def __init__(self, **kwargs):
         kwargs.update({'board_name': os.path.basename(__file__).rstrip('.py')})
         super().__init__(**kwargs)
+        self.exec_logger.event(f'{self.board_name}\ttx_init\tbegin\t{datetime.datetime.utcnow()}')
         self._voltage = kwargs.pop('voltage', TX_CONFIG['default_voltage'])
-        self.ctl = kwargs.pop('controller', ctl_module.Ctl())
+        self.voltage_adjustable = False
+        self.current_adjustable = False
+        if self.ctl is None:
+            self.ctl = ctl_module.Ctl()
+        # elif isinstance(self.ctl, dict):
+        #     self.ctl = ctl_module.Ctl(**self.ctl)
 
         # I2C connexion to MCP23008, for current injection
         self.mcp_board = MCP23008(self.ctl.bus, address=TX_CONFIG['mcp_board_address'])
-
         # ADS1115 for current measurement (AB)
-        self._adc_gain = 2/3
         self._ads_current_address = 0x48
         self._ads_current = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_current_address)
+        self._ads_current.mode = Mode.CONTINUOUS
 
         # Relays for pulse polarity
         self.pin0 = self.mcp_board.get_pin(0)
@@ -89,31 +111,18 @@ class Tx(TxAbstract):
         self.pin1 = self.mcp_board.get_pin(1)
         self.pin1.direction = Direction.OUTPUT
         self.polarity = 0
+        self.adc_gain = 2 / 3
 
-        # DPH 5005 Digital Power Supply
-        self.pin2 = self.mcp_board.get_pin(2)  # dps +
-        self.pin2.direction = Direction.OUTPUT
-        self.pin3 = self.mcp_board.get_pin(3)  # dps -
-        self.pin3.direction = Direction.OUTPUT
-        self.turn_on()
-        time.sleep(TX_CONFIG['dps_switch_on_warm_up'])
-        self.DPS = minimalmodbus.Instrument(port='/dev/ttyUSB0', slaveaddress=1)  # port name, address (decimal)
-        self.DPS.serial.baudrate = 9600  # Baud rate 9600 as listed in doc
-        self.DPS.serial.bytesize = 8  #
-        self.DPS.serial.timeout = 1.  # greater than 0.5 for it to work
-        self.DPS.debug = False  #
-        self.DPS.serial.parity = 'N'  # No parity
-        self.DPS.mode = minimalmodbus.MODE_RTU  # RTU mode
-        self.DPS.write_register(0x0001, 1000, 0)  # max current allowed (100 mA for relays) :
-        # (last number) 0 is for mA, 3 is for A
+        self.pwr = None  # TODO: set a list of compatible power system with the tx
 
-        # I2C connexion to MCP23008, for current injection
+        # Initialize LEDs
         self.pin4 = self.mcp_board.get_pin(4)  # Ohmpi_run
         self.pin4.direction = Direction.OUTPUT
         self.pin4.value = True
 
-        self.exec_logger.info(f'TX battery: {self.tx_bat:.1f} V')
-        self.turn_off()
+        self._latency = kwargs.pop('latency', TX_CONFIG['latency'])
+        self._bias = kwargs.pop('bias', TX_CONFIG['bias'])
+        self.exec_logger.event(f'{self.board_name}\ttx_init\tend\t{datetime.datetime.utcnow()}')
 
     @property
     def adc_gain(self):
@@ -125,12 +134,15 @@ class Tx(TxAbstract):
         self._adc_gain = value
         self._ads_current = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_current_address)
+        self._ads_current.mode = Mode.CONTINUOUS
         self.exec_logger.debug(f'Setting TX ADC gain to {value}')
 
     def adc_gain_auto(self):
+        self.exec_logger.event(f'{self.board_name}\ttx_adc_auto_gain\tbegin\t{datetime.datetime.utcnow()}')
         gain = _gain_auto(AnalogIn(self._ads_current, ads.P0))
         self.exec_logger.debug(f'Setting TX ADC gain automatically to {gain}')
         self.adc_gain = gain
+        self.exec_logger.event(f'{self.board_name}\ttx_adc_auto_gain\tend\t{datetime.datetime.utcnow()}')
 
     def current_pulse(self, **kwargs):
         TxAbstract.current_pulse(self, **kwargs)
@@ -150,65 +162,43 @@ class Tx(TxAbstract):
         self.exec_logger.warning(f'Current pulse is not implemented for the {TX_CONFIG["model"]} board')
 
     def inject(self, polarity=1, injection_duration=None):
+        self.polarity = polarity
         TxAbstract.inject(self, polarity=polarity, injection_duration=injection_duration)
-        # move this part in DPS5005
-        # if state=='on':
-        #     self.DPS.write_register(0x09, 1)  # DPS5005 on
-        # else:
-        #     self.DPS.write_register(0x09, 0)  # DPS5005 off
 
     @property
     def polarity(self):
-        return TxAbstract.polarity.fget(self)
+        return self._polarity
 
     @polarity.setter
-    def polarity(self, value):
-        TxAbstract.polarity.fset(self, value)
-        if value==1:
+    def polarity(self, polarity):
+        assert polarity in [-1, 0, 1]
+        self._polarity = polarity
+        if polarity == 1:
             self.pin0.value = True
             self.pin1.value = False
-        elif value==-1:
+            time.sleep(0.005)  # Max turn on time of 211EH relays = 5ms
+        elif polarity == -1:
             self.pin0.value = False
             self.pin1.value = True
+            time.sleep(0.005)  # Max turn on time of 211EH relays = 5ms
         else:
             self.pin0.value = False
             self.pin1.value = False
-        #time.sleep(0.001) # TODO: check max switching time of relays
-
-    @property
-    def voltage(self):
-        return self._voltage
-    @voltage.setter
-    def voltage(self, value):
-        if value > TX_CONFIG['voltage_max']:
-            self.exec_logger.warning(f'Sorry, cannot inject more than {TX_CONFIG["voltage_max"]} V, '
-                                     f'set it back to {TX_CONFIG["default_voltage"]} V (default value).')
-            value = TX_CONFIG['default_voltage']
-        if value < 0.:
-            self.exec_logger.warning(f'Voltage should be given as a positive number. '
-                                     f'Set polarity to -1 to reverse voltage...')
-            value = np.abs(value)
-
-        self.DPS.write_register(0x0000, value, 2)
+            time.sleep(0.001)  # Max turn off time of 211EH relays = 1ms
 
     def turn_off(self):
-        TxAbstract.turn_off(self)
-        self.pin2.value = False
-        self.pin3.value = False
+        self.pwr.turn_off(self)
 
     def turn_on(self):
-        TxAbstract.turn_on(self)
-        self.pin2.value = True
-        self.pin3.value = True
+        self.pwr.turn_on(self)
 
     @property
     def tx_bat(self):
-        tx_bat = self.DPS.read_register(0x05, 2)
-        if tx_bat < TX_CONFIG['low_battery']:
-            self.soh_logger.warning(f'Low TX Battery: {tx_bat:.1f} V')
-        return tx_bat
+        self.soh_logger.warning(f'Cannot get battery voltage on {self.board_name}')
+        self.exec_logger.debug(f'{self.board_name} cannot read battery voltage. Returning default battery voltage.')
+        return TX_CONFIG['low_battery']
 
-    def voltage_pulse(self, voltage=TX_CONFIG['default_voltage'], length=None, polarity=None):
+    def voltage_pulse(self, voltage=TX_CONFIG['default_voltage'], length=None, polarity=1):
         """ Generates a square voltage pulse
 
         Parameters
@@ -220,28 +210,38 @@ class Tx(TxAbstract):
         polarity: 1,0,-1
             Polarity of the pulse
         """
-
+        self.exec_logger.event(f'{self.board_name}\ttx_voltage_pulse\tbegin\t{datetime.datetime.utcnow()}')
+        # self.exec_logger.info(f'injection_duration: {length}')  # TODO: delete me
         if length is None:
             length = self.injection_duration
-        if polarity is None:
-            polarity = self.polarity
-        self.polarity = polarity
-        self.voltage = voltage
-        self.exec_logger.debug(f'Voltage pulse of {polarity*voltage:.3f} V for {length:.3f} s')
-        self.inject(state='on')
-        time.sleep(length)
-        self.inject(state='off')
+        self.pwr.voltage = voltage
+        self.exec_logger.debug(f'Voltage pulse of {polarity*self.pwr.voltage:.3f} V for {length:.3f} s')
+        self.inject(polarity=polarity, injection_duration=length)
+        self.exec_logger.event(f'{self.board_name}\ttx_voltage_pulse\tend\t{datetime.datetime.utcnow()}')
+
 
 class Rx(RxAbstract):
     def __init__(self, **kwargs):
         kwargs.update({'board_name': os.path.basename(__file__).rstrip('.py')})
         super().__init__(**kwargs)
-        self.ctl = kwargs.pop('controller', ctl_module.Ctl())
+        self.exec_logger.event(f'{self.board_name}\trx_init\tbegin\t{datetime.datetime.utcnow()}')
+        if self.ctl is None:
+            self.ctl = ctl_module.Ctl()
+        # I2C connexion to MCP23008, for DG411
+        self.mcp_board = MCP23008(self.ctl.bus, address=RX_CONFIG['mcp_board_address'])
 
         # ADS1115 for voltage measurement (MN)
         self._ads_voltage_address = 0x49
         self._adc_gain = 2/3
-        self._ads_voltage = ads.ADS1115(self.ctl.bus, gain=self._adc_gain, data_rate=860, address=self._ads_voltage_address)
+        self._ads_voltage = ads.ADS1115(self.ctl.bus, gain=self._adc_gain, data_rate=860,
+                                        address=self._ads_voltage_address)
+        self._ads_voltage.mode = Mode.CONTINUOUS
+        self._coef_p2 = kwargs.pop('coef_p2', RX_CONFIG['coef_p2'])
+        self._voltage_max = kwargs.pop('voltage_max', RX_CONFIG['voltage_max'])
+        self._sampling_rate = kwargs.pop('sampling_rate', sampling_rate)
+        self._latency = kwargs.pop('latency', RX_CONFIG['latency'])
+        self._bias = kwargs.pop('bias', RX_CONFIG['bias'])
+        self.exec_logger.event(f'{self.board_name}\trx_init\tend\t{datetime.datetime.utcnow()}')
 
     @property
     def adc_gain(self):
@@ -253,21 +253,23 @@ class Rx(RxAbstract):
         self._adc_gain = value
         self._ads_voltage = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_voltage_address)
+        self._ads_voltage.mode = Mode.CONTINUOUS
         self.exec_logger.debug(f'Setting RX ADC gain to {value}')
 
     def adc_gain_auto(self):
+        self.exec_logger.event(f'{self.board_name}\trx_adc_auto_gain\tbegin\t{datetime.datetime.utcnow()}')
         gain_0 = _gain_auto(AnalogIn(self._ads_voltage, ads.P0))
         gain_2 = _gain_auto(AnalogIn(self._ads_voltage, ads.P2))
         gain = np.min([gain_0, gain_2])
         self.exec_logger.debug(f'Setting RX ADC gain automatically to {gain}')
         self.adc_gain = gain
+        self.exec_logger.event(f'{self.board_name}\trx_adc_auto_gain\tend\t{datetime.datetime.utcnow()}')
 
     @property
     def voltage(self):
         """ Gets the voltage VMN in Volts
         """
-        u0 = AnalogIn(self._ads_voltage, ads.P0).voltage * 1000.
-        u2 = AnalogIn(self._ads_voltage, ads.P2).voltage * 1000.
-        u = np.max([u0,u2]) * (np.heaviside(u0-u2, 1.) * 2 - 1.) # gets the max between u0 & u2 and set the sign
-        self.exec_logger.debug(f'Reading voltages {u0} V and {u2} V on RX. Returning {u} V')
-        return u
\ No newline at end of file
+        self.exec_logger.event(f'{self.board_name}\trx_voltage\tbegin\t{datetime.datetime.utcnow()}')
+        u = -AnalogIn(self._ads_voltage, ads.P0, ads.P1).voltage * self._coef_p2 * 1000. - self._bias  # TODO: check if it should be negated
+        self.exec_logger.event(f'{self.board_name}\trx_voltage\tend\t{datetime.datetime.utcnow()}')
+        return u
diff --git a/ohmpi/hardware_components/mux_2023_rev_0_0.py b/ohmpi/hardware_components/mux_2023_rev_0_0.py
index 322a386c88e57badc672e312096887d3c79fa95c..63f1f4fd020778cf6947228bc82baa9a8a042e6a 100644
--- a/ohmpi/hardware_components/mux_2023_rev_0_0.py
+++ b/ohmpi/hardware_components/mux_2023_rev_0_0.py
@@ -71,7 +71,7 @@ class Mux(MuxAbstract):
         else:
             self.exec_logger.error(f'Invalid role assignment for {self.board_name}: {self._roles} !')
             self._mode = ''
-        self._tca = [adafruit_tca9548a.TCA9548A(self.ctl.bus, tca_address)[i] for i in np.arange(7,3,-1)]
+        self._tca = [adafruit_tca9548a.TCA9548A(self.ctl.bus, tca_address)[i] for i in np.arange(7, 3, -1)]
         # self._mcp_addresses = (kwargs.pop('mcp', '0x20'))  # TODO: add assert on valid addresses..
         self._mcp = [None, None, None, None]
         self.reset()
diff --git a/ohmpi/hardware_components/ohmpi_card_3_15.py b/ohmpi/hardware_components/ohmpi_card_3_15.py
index 2dbb16d7e570a39b001bb14265078c0ca7c34709..d8a63edf4e70d0b9df855afe22d9a23c434f5f9d 100644
--- a/ohmpi/hardware_components/ohmpi_card_3_15.py
+++ b/ohmpi/hardware_components/ohmpi_card_3_15.py
@@ -6,12 +6,12 @@ from adafruit_ads1x15.analog_in import AnalogIn  # noqa
 from adafruit_ads1x15.ads1x15 import Mode  # noqa
 from adafruit_mcp230xx.mcp23008 import MCP23008  # noqa
 from digitalio import Direction  # noqa
-import minimalmodbus  # noqa
 import time
 import numpy as np
 import os
 from ohmpi.hardware_components import TxAbstract, RxAbstract
-ctl_name = HARDWARE_CONFIG['ctl'].pop('board_name', 'raspberry_pi_i2c')
+ctl_name = HARDWARE_CONFIG['ctl'].pop('board_name', 'raspberry_pi')
+ctl_connection = HARDWARE_CONFIG['ctl'].pop('connection', 'i2c')
 ctl_module = importlib.import_module(f'ohmpi.hardware_components.{ctl_name}')
 
 TX_CONFIG = HARDWARE_CONFIG['tx']
@@ -96,12 +96,13 @@ class Tx(TxAbstract):
             self.ctl = ctl_module.Ctl()
         # elif isinstance(self.ctl, dict):
         #     self.ctl = ctl_module.Ctl(**self.ctl)
+        self.io = self.ctl[kwargs.pop('connection', ctl_connection)]
 
         # I2C connexion to MCP23008, for current injection
-        self.mcp_board = MCP23008(self.ctl.bus, address=TX_CONFIG['mcp_board_address'])
+        self.mcp_board = MCP23008(self.io, address=TX_CONFIG['mcp_board_address'])
         # ADS1115 for current measurement (AB)
         self._ads_current_address = 0x48
-        self._ads_current = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
+        self._ads_current = ads.ADS1115(self.io, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_current_address)
         self._ads_current.mode = Mode.CONTINUOUS
 
@@ -115,10 +116,11 @@ class Tx(TxAbstract):
 
         self.pwr = None  # TODO: set a list of compatible power system with the tx
 
-        # I2C connexion to MCP23008, for current injection
-        self.pin4 = self.mcp_board.get_pin(4)  # Ohmpi_run
+        # MCP23008 pins for LEDs
+        self.pin4 = self.mcp_board.get_pin(4)  # TODO: Delete me? No LED on this version of the board
         self.pin4.direction = Direction.OUTPUT
         self.pin4.value = True
+
         self._latency = kwargs.pop('latency', TX_CONFIG['latency'])
         self._bias = kwargs.pop('bias', TX_CONFIG['bias'])
         self.exec_logger.event(f'{self.board_name}\ttx_init\tend\t{datetime.datetime.utcnow()}')
@@ -131,7 +133,7 @@ class Tx(TxAbstract):
     def adc_gain(self, value):
         assert value in [2/3, 2, 4, 8, 16]
         self._adc_gain = value
-        self._ads_current = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
+        self._ads_current = ads.ADS1115(self.io, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_current_address)
         self._ads_current.mode = Mode.CONTINUOUS
         self.exec_logger.debug(f'Setting TX ADC gain to {value}')
@@ -226,10 +228,12 @@ class Rx(RxAbstract):
         self.exec_logger.event(f'{self.board_name}\trx_init\tbegin\t{datetime.datetime.utcnow()}')
         if self.ctl is None:
             self.ctl = ctl_module.Ctl()
+        self.io = self.ctl[kwargs.pop('connection', ctl_connection)]
+
         # ADS1115 for voltage measurement (MN)
         self._ads_voltage_address = 0x49
         self._adc_gain = 2/3
-        self._ads_voltage = ads.ADS1115(self.ctl.bus, gain=self._adc_gain, data_rate=860,
+        self._ads_voltage = ads.ADS1115(self.io, gain=self._adc_gain, data_rate=860,
                                         address=self._ads_voltage_address)
         self._ads_voltage.mode = Mode.CONTINUOUS
         self._coef_p2 = kwargs.pop('coef_p2', RX_CONFIG['coef_p2'])
@@ -247,7 +251,7 @@ class Rx(RxAbstract):
     def adc_gain(self, value):
         assert value in [2/3, 2, 4, 8, 16]
         self._adc_gain = value
-        self._ads_voltage = ads.ADS1115(self.ctl.bus, gain=self.adc_gain, data_rate=860,
+        self._ads_voltage = ads.ADS1115(self.io, gain=self.adc_gain, data_rate=860,
                                         address=self._ads_voltage_address)
         self._ads_voltage.mode = Mode.CONTINUOUS
         self.exec_logger.debug(f'Setting RX ADC gain to {value}')
diff --git a/ohmpi/hardware_components/raspberry_pi.py b/ohmpi/hardware_components/raspberry_pi.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf7eaa8ed87d33c8db7b9d81277939f2603c9637
--- /dev/null
+++ b/ohmpi/hardware_components/raspberry_pi.py
@@ -0,0 +1,46 @@
+from ohmpi.hardware_components import CtlAbstract
+import board  # noqa
+import busio  # noqa
+from adafruit_extended_bus import ExtendedI2C  # noqa
+import minimalmodbus  # noqa
+import os
+from ohmpi.utils import get_platform
+from gpiozero import CPUTemperature  # noqa
+
+
+class Ctl(CtlAbstract):
+    def __init__(self, **kwargs):
+        kwargs.update({'board_name': os.path.basename(__file__).rstrip('.py')})
+        modbus_baudrate = kwargs.pop('modbus_baudrate', 9600)
+        modbus_bitesize = kwargs.pop('modbus_bitesize', 8)
+        modbus_timeout = kwargs.pop('modbus_timeout', 1)
+        modbus_debug = kwargs.pop('modbus_debug', False)
+        modbus_parity = kwargs.pop('modbus_parity', 'N')
+        modbus_mode = kwargs.pop('modbus_mode', minimalmodbus.MODE_RTU)
+        modbus_port = kwargs.pop('modbus_port', '/dev/ttyUSB0')
+        modbus_slave_address = kwargs.pop('modbus_slave_address', 1)
+
+        super().__init__(**kwargs)
+        self.connections = dict()
+        # I2C
+        self.connections['i2c'] = I2C(board.SCL, board.SDA)  # noqa
+        # Extended I2C
+        self.connections['i2c_ext'] = ExtendedI2C(4)  # 4 is defined
+        # modbus
+        self.connections['modbus'] = minimalmodbus.Instrument(port=modbus_port, slaveaddress=modbus_slave_address)
+        self.connections['modbus'].serial.baudrate = modbus_baudrate  # Baud rate 9600 as listed in doc
+        self.connections['modbus'].serial.bytesize = modbus_bitesize  #
+        self.connections['modbus'].serial.timeout = modbus_timeout  # greater than 0.5 for it to work
+        self.connections['modbus'].debug = modbus_debug  #
+        self.connections['modbus'].serial.parity = modbus_parity  # No parity
+        self.connections['modbus'].mode = modbus_mode  # RTU mode
+
+        platform, on_pi = get_platform()
+        assert on_pi
+        self.board_name = platform
+        self._cpu_temp_available = True
+        self.max_cpu_temp = 85.  # °C
+
+    @property
+    def _cpu_temp(self):
+        return CPUTemperature().temperature
diff --git a/ohmpi/hardware_components/raspberry_pi_modbus.py b/ohmpi/hardware_components/raspberry_pi_modbus.py
index 4c5405441890dabe1cf70e6d686f62cb4582b481..b6e4fb0ef28da2e9f258132bd8345ff1e5e9355b 100644
--- a/ohmpi/hardware_components/raspberry_pi_modbus.py
+++ b/ohmpi/hardware_components/raspberry_pi_modbus.py
@@ -8,7 +8,6 @@ from gpiozero import CPUTemperature  # noqa
 import minimalmodbus  # noqa
 
 
-
 class Ctl(CtlAbstract):
     def __init__(self, **kwargs):
         kwargs.update({'board_name': os.path.basename(__file__).rstrip('.py')})
@@ -20,8 +19,6 @@ class Ctl(CtlAbstract):
         mode = kwargs.pop('mode', minimalmodbus.MODE_RTU)
         port = kwargs.pop('port', '/dev/ttyUSB0')
         slave_address = kwargs.pop('slave_address', 1)
-        port = kwargs.pop('port', '/dev/ttyUSB0')
-        slave_address = kwargs.pop('slave_address', 1)
         super().__init__(**kwargs)
         self.bus = minimalmodbus.Instrument(port=port, slaveaddress=slave_address)  # port name, address (decimal)
         self.bus.serial.baudrate = baudrate  # Baud rate 9600 as listed in doc
diff --git a/ohmpi/ohmpi.py b/ohmpi/ohmpi.py
index 54e3b23cd30dd0eddd7738d24d513b26397e3e96..c176d901dc21f9c236d11573318ad1d7962c31ec 100644
--- a/ohmpi/ohmpi.py
+++ b/ohmpi/ohmpi.py
@@ -413,6 +413,9 @@ 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,
                         cmd_id=None, **kwargs):
+        # TODO: add sampling_interval -> impact on _hw.rx.sampling_rate (store the current value, change the _hw.rx.sampling_rate, do the measurement, reset the sampling_rate to the previous value)
+        # TODO: default value of tx_volt and other parameters set to None should be given in config.py and used in function definition
+        # TODO: add rs_check option (or propose an other way to do this)
         """Measures on a quadrupole and returns transfer resistance.
 
         Parameters
@@ -669,6 +672,7 @@ class OhmPi(object):
     #  isolate electrodes that are responsible for high resistances (ex: AB high, AC low, BC high
     #  -> might be a problem at B (cf what we did with WofE)
     def rs_check(self, tx_volt=12., cmd_id=None):
+        # TODO: add a default value for rs-check in config.py import it in ohmpi.py and add it in rs_check definition
         """Checks contact resistances
 
         Parameters