diff --git a/ohmpi/hardware_components/abstract_hardware_components.py b/ohmpi/hardware_components/abstract_hardware_components.py index bf8906ed5d162f6c26abebd50a981677b0efb998..93ee25c3ec0396edaf8bed943686a81d5af806b6 100644 --- a/ohmpi/hardware_components/abstract_hardware_components.py +++ b/ohmpi/hardware_components/abstract_hardware_components.py @@ -62,6 +62,7 @@ class PwrAbstract(ABC): self.switchable = False self.connection = kwargs.pop('connection', None) self._battery_voltage = np.nan + self._pwr_discharge_latency = np.nan @property @abstractmethod @@ -392,6 +393,17 @@ class TxAbstract(ABC): def measuring(self, mode="off"): self._measuring = mode + def discharge_pwr(self, latency=None): + if self.pwr.voltage_adjustable: + if latency is None: + latency = self.pwr._pwr_discharge_latency + self.exec_logger.debug(f'Pwr discharge initiated for {latency} s') + + time.sleep(latency) + + else: + self.exec_logger.debug(f'Pwr discharge not supported by {self.pwr.model}') + @property def polarity(self): return self._polarity diff --git a/ohmpi/hardware_components/mb_2024_0_2.py b/ohmpi/hardware_components/mb_2024_0_2.py index 5b9f4509ae6d88bce9a711c9def337c06629792c..8489eb86e35d21e5c8bdd6c6cd7538b60ff5d9cb 100644 --- a/ohmpi/hardware_components/mb_2024_0_2.py +++ b/ohmpi/hardware_components/mb_2024_0_2.py @@ -36,7 +36,6 @@ SPECS = {'rx': {'model': {'default': os.path.basename(__file__).rstrip('.py')}, 'r_shunt': {'min': 0.001, 'default': 2.}, 'activation_delay': {'default': 0.010}, # Max turn on time of OMRON G5LE-1 5VDC relays 'release_delay': {'default': 0.005}, # Max turn off time of OMRON G5LE-1 5VDC relays = 1ms - 'pwr_latency': {'default': 4.} }} # TODO: move low_battery spec in pwr @@ -80,7 +79,6 @@ class Tx(Tx_mb_2023): super().__init__(**kwargs) if not subclass_init: self.exec_logger.event(f'{self.model}\ttx_init\tbegin\t{datetime.datetime.utcnow()}') - self._pwr_latency = kwargs['pwr_latency'] # Initialize LEDs self.pin4 = self.mcp_board.get_pin(4) # OhmPi_run @@ -116,6 +114,13 @@ class Tx(Tx_mb_2023): elif mode == "off": self.pin5.value = False + def discharge_pwr(self, latency=None): + if latency is None: + latency = self.pwr._pwr_discharge_latency + + time.sleep(latency) + + def inject(self, polarity=1, injection_duration=None): # add leds? self.pin6.value = True diff --git a/ohmpi/hardware_components/mb_2024_1_X.py b/ohmpi/hardware_components/mb_2024_1_X.py new file mode 100644 index 0000000000000000000000000000000000000000..93fd1fd1a29ea9bfb950eeec31b4b4b4bd49e20b --- /dev/null +++ b/ohmpi/hardware_components/mb_2024_1_X.py @@ -0,0 +1,127 @@ +import datetime +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 +from busio import I2C # noqa +import os +import time +from ohmpi.utils import enforce_specs +from ohmpi.hardware_components.mb_2024_0_2 import Tx as Tx_mb_2024_0_2 +from ohmpi.hardware_components.mb_2024_0_2 import Rx as Rx_mb_2024_0_2 + +# hardware characteristics and limitations +# voltages are given in mV, currents in mA, sampling rates in Hz and data_rate in S/s +SPECS = {'rx': {'model': {'default': os.path.basename(__file__).rstrip('.py')}, + 'sampling_rate': {'min': 0., 'default': 100., 'max': 500.}, + 'data_rate': {'default': 860.}, + 'bias': {'min': -5000., 'default': 0., 'max': 5000.}, + 'coef_p2': {'default': 1.00}, + 'mcp_address': {'default': 0x27}, + 'ads_address': {'default': 0x49}, + 'voltage_min': {'default': 10.0}, + 'voltage_max': {'default': 5000.0}, # [mV] + 'dg411_gain_ratio': {'default': 1/2}, # lowest resistor value over sum of resistor values + 'vmn_hardware_offset': {'default': 2500.}, + }, + 'tx': {'model': {'default': os.path.basename(__file__).rstrip('.py')}, + 'adc_voltage_min': {'default': 10.}, # Minimum voltage value used in vmin strategy + 'adc_voltage_max': {'default': 4500.}, # Maximum voltage on ads1115 used to measure current + 'voltage_max': {'min': 0., 'default': 12., 'max': 50.}, # Maximum input voltage + 'data_rate': {'default': 860.}, + 'mcp_address': {'default': 0x21}, + 'ads_address': {'default': 0x48}, + 'compatible_power_sources': {'default': ['pwr_batt', 'dps5005']}, + 'r_shunt': {'min': 0.001, 'default': 2.}, + 'activation_delay': {'default': 0.010}, # Max turn on time of OMRON G5LE-1 5VDC relays + 'release_delay': {'default': 0.005}, # Max turn off time of OMRON G5LE-1 5VDC relays = 1ms + }} + +# TODO: move low_battery spec in pwr + + +def _ads_1115_gain_auto(channel): # Make it a class method ? + """Automatically sets the gain on a channel + + Parameters + ---------- + channel : ads.ADS1x15 + Instance of ADS where voltage is measured. + + Returns + ------- + gain : float + Gain to be applied on ADS1115. + """ + + gain = 2 / 3 + if (abs(channel.voltage) < 2.048) and (abs(channel.voltage) >= 1.024): + gain = 2 + elif (abs(channel.voltage) < 1.024) and (abs(channel.voltage) >= 0.512): + gain = 4 + elif (abs(channel.voltage) < 0.512) and (abs(channel.voltage) >= 0.256): + gain = 8 + elif abs(channel.voltage) < 0.256: + gain = 16 + return gain + + +class Tx(Tx_mb_2024_0_2): + """TX Class""" + def __init__(self, **kwargs): + if 'model' not in kwargs.keys(): + for key in SPECS['tx'].keys(): + kwargs = enforce_specs(kwargs, SPECS['tx'], key) + subclass_init = False + else: + subclass_init = True + super().__init__(**kwargs) + if not subclass_init: + self.exec_logger.event(f'{self.model}\ttx_init\tbegin\t{datetime.datetime.utcnow()}') + + self.pin5 = self.mcp_board.get_pin(5) # power_discharge_relay + self.pin5.direction = Direction.OUTPUT + self.pin5.value = False + + if not subclass_init: + self.exec_logger.event(f'{self.model}\ttx_init\tend\t{datetime.datetime.utcnow()}') + + @property + def measuring(self): + return self._measuring + + @measuring.setter + def measuring(self, mode="off"): + self._measuring = mode + + + def discharge_pwr(self, latency=None): + if self.pwr.voltage_adjustable: + if latency is None: + latency = self.pwr._pwr_discharge_latency + self.exec_logger.debug(f'Pwr discharge initiated for {latency} s') + + self.exec_logger.event(f'{self.model}\tpwr_discharge\tend\t{datetime.datetime.utcnow()}') + self.pin5.value = True + time.sleep(self._activation_delay) + + time.sleep(latency) + + if self.pwr.voltage_adjustable: + self.pin5.value = False + time.sleep(self._release_delay) + self.exec_logger.event(f'{self.model}\tpwr_discharge\tend\t{datetime.datetime.utcnow()}') + else: + self.exec_logger.debug(f'Pwr discharge not supported by {self.pwr.model}') + +class Rx(Rx_mb_2024_0_2): + """RX Class""" + def __init__(self, **kwargs): + if 'model' not in kwargs.keys(): + for key in SPECS['rx'].keys(): + kwargs = enforce_specs(kwargs, SPECS['rx'], key) + subclass_init = False + else: + subclass_init = True + super().__init__(**kwargs) diff --git a/ohmpi/hardware_components/pwr_dps5005.py b/ohmpi/hardware_components/pwr_dps5005.py index 5dc5073f4803e3268d20912ab74ebd9a757a46e5..8a47cdf45cad125cb6807014379a60528447d3a1 100644 --- a/ohmpi/hardware_components/pwr_dps5005.py +++ b/ohmpi/hardware_components/pwr_dps5005.py @@ -14,7 +14,8 @@ SPECS = {'model': {'default': os.path.basename(__file__).rstrip('.py')}, 'current_max': {'default': 60.}, 'current_adjustable': {'default': False}, 'voltage_adjustable': {'default': True}, - 'pwr_latency': {'default': 0.} + 'pwr_latency': {'default': 4.}, + 'pwr_discharge_latency': {'default': 1.} } @@ -37,6 +38,7 @@ class Pwr(PwrAbstract): self._current = np.nan self._pwr_state = 'off' self._pwr_latency = kwargs['pwr_latency'] + self._pwr_discharge_latency = kwargs['pwr_discharge_latency'] if not subclass_init: self.exec_logger.event(f'{self.model}\tpwr_init\tend\t{datetime.datetime.utcnow()}') @@ -97,9 +99,9 @@ class Pwr(PwrAbstract): self.exec_logger.event(f'{self.model}\tpwr_state_on\tend\t{datetime.datetime.utcnow()}') # self.current_max(self._current_max) self._pwr_state = 'on' - self.exec_logger.event(f'{self.model}\tpwr_latency\tbegin\t{datetime.datetime.utcnow()}') - time.sleep(self._pwr_latency) - self.exec_logger.event(f'{self.model}\tpwr_latency\tend\t{datetime.datetime.utcnow()}') + # self.exec_logger.event(f'{self.model}\tpwr_latency\tbegin\t{datetime.datetime.utcnow()}') + # time.sleep(self._pwr_latency) + # self.exec_logger.event(f'{self.model}\tpwr_latency\tend\t{datetime.datetime.utcnow()}') self.exec_logger.debug(f'{self.model} is on') elif state == 'off': diff --git a/ohmpi/hardware_system.py b/ohmpi/hardware_system.py index 38e2dd6d06f9033ec594e552b19b80096af7c9e5..33d8f5b83e1ff91f0cb15e3f81671552dafa48ca 100644 --- a/ohmpi/hardware_system.py +++ b/ohmpi/hardware_system.py @@ -585,7 +585,8 @@ class OhmPiHardware: ### if discharge relay manually add on mb_2024_0_2, then should not activate AB relays but simply wait for automatic discharge ### if mb_20240_1_X then TX should handle the pwr discharge - time.sleep(1.0) + # time.sleep(1.0) + self.tx.discharge_pwr() def _plot_readings(self, save_fig=False, filename=None): # Plot graphs