hitl_tester.modules.bms.ngi_sim

Provides controls for the NGI N83624 Cell Simulator.

(c) 2020-2024 TurnAround Factor, Inc.

#

CUI DISTRIBUTION CONTROL

Controlled by: DLA J68 R&D SBIP

CUI Category: Small Business Research and Technology

Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS

POC: GOV SBIP Program Manager Denise Price, 571-767-0111

Distribution authorized to U.S. Government Agencies only, to protect information not owned by the

U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that

it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests

for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,

Fort Belvoir, VA 22060-6221

#

SBIR DATA RIGHTS

Contract No.:SP4701-23-C-0083

Contractor Name: TurnAround Factor, Inc.

Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005

Expiration of SBIR Data Rights Period: September 24, 2029

The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer

software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights

in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause

contained in the above identified contract. No restrictions apply after the expiration date shown above. Any

reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce

the markings.

  1"""
  2Provides controls for the NGI N83624 Cell Simulator.
  3
  4# (c) 2020-2024 TurnAround Factor, Inc.
  5#
  6# CUI DISTRIBUTION CONTROL
  7# Controlled by: DLA J68 R&D SBIP
  8# CUI Category: Small Business Research and Technology
  9# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
 10# POC: GOV SBIP Program Manager Denise Price, 571-767-0111
 11# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
 12# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
 13# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
 14# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
 15# Fort Belvoir, VA 22060-6221
 16#
 17# SBIR DATA RIGHTS
 18# Contract No.:SP4701-23-C-0083
 19# Contractor Name: TurnAround Factor, Inc.
 20# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
 21# Expiration of SBIR Data Rights Period: September 24, 2029
 22# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
 23# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
 24# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
 25# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
 26# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
 27# the markings.
 28"""
 29
 30import time
 31
 32from hitl_tester.modules.bms.cell import Cell
 33from hitl_tester.modules.bms_types import UnderVoltageError, OverVoltageError, CellCompMode, NGIMode
 34
 35
 36class NGICell(Cell):
 37    """NGI N83624 Cell Simulator command wrapper."""
 38
 39    @property
 40    def measured_volts(self) -> float:
 41        """Measures actual cell voltage."""
 42        with self._lock:
 43            result = float(self._resource.query(f"MEAS{self.id}:VOLT?"))
 44            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 45
 46    @property
 47    def volts(self) -> float:
 48        """Gets the last target voltage."""
 49        with self._lock:
 50            return self._volts
 51
 52    @volts.setter
 53    def volts(self, new_voltage: float):
 54        """Sets the output voltage."""
 55        with self._lock:
 56            if not self.disengage_safety_protocols and new_voltage <= self.data.uv_protection:
 57                raise UnderVoltageError(
 58                    f"Undervoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}. "
 59                    f"Voltage {new_voltage} is lower than {self.data.uv_protection}."
 60                )
 61            if not self.disengage_safety_protocols and new_voltage >= self.data.ov_protection:
 62                raise OverVoltageError(
 63                    f"Overvoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}. "
 64                    f"Voltage {new_voltage} is higher than {self.data.ov_protection}."
 65                )
 66
 67            self._resource.write(f"CHAR{self.id}:VOLT {new_voltage}")
 68            self._volts = new_voltage
 69
 70    @property
 71    def ohms(self) -> float:
 72        """Measures internal resistance of the cell"""
 73        with self._lock:
 74            return self._resistance  #  MEAS1:R?
 75
 76    @ohms.setter
 77    def ohms(self, new_ohms: float):
 78        """Sets the internal resistance of the cell."""
 79        with self._lock:
 80            self._resistance = new_ohms
 81            self._resource.write(f"CHAR{self.id}:R {new_ohms}")
 82
 83    @property
 84    def amps(self) -> float:
 85        """Measures cell current."""
 86        with self._lock:
 87            result = float(self._resource.query(f"MEAS{self.id}:CURR?"))
 88            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 89
 90    @amps.setter
 91    def amps(self, new_amps: float):
 92        """Sets the current limit of the cell."""
 93        with self._lock:
 94            self._resource.write(f"CHAR{self.id}:OUTCURR {new_amps}")
 95
 96    @property
 97    def compensation_mode(self) -> CellCompMode:
 98        """Sets compensation mode"""
 99        return CellCompMode.LLOCAL
100
101    @compensation_mode.setter
102    def compensation_mode(self, mode: CellCompMode):
103        """Sets compensation mode"""
104
105    def enable(self):
106        """Enables the cell."""
107        self._resource.write(f"OUTP{self.id}:ONOFF 1")
108        self._thread_pause_flag.set()
109
110    def disable(self):
111        """Disables the cell."""
112        self._thread_pause_flag.clear()
113        self._resource.write(f"OUTP{self.id}:ONOFF 0")
114
115    def reset(self):
116        """Resets the instrument"""
117        self._resource.write("*RST")
118        self._resource.write(  # Set all channels to charge/discharge mode
119            f"OUTP:MODE {NGIMode.CHARGE}(@1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)"
120        )
121        time.sleep(6)  # Needs delay after reset
class NGICell(hitl_tester.modules.bms.cell.Cell):
 37class NGICell(Cell):
 38    """NGI N83624 Cell Simulator command wrapper."""
 39
 40    @property
 41    def measured_volts(self) -> float:
 42        """Measures actual cell voltage."""
 43        with self._lock:
 44            result = float(self._resource.query(f"MEAS{self.id}:VOLT?"))
 45            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 46
 47    @property
 48    def volts(self) -> float:
 49        """Gets the last target voltage."""
 50        with self._lock:
 51            return self._volts
 52
 53    @volts.setter
 54    def volts(self, new_voltage: float):
 55        """Sets the output voltage."""
 56        with self._lock:
 57            if not self.disengage_safety_protocols and new_voltage <= self.data.uv_protection:
 58                raise UnderVoltageError(
 59                    f"Undervoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}. "
 60                    f"Voltage {new_voltage} is lower than {self.data.uv_protection}."
 61                )
 62            if not self.disengage_safety_protocols and new_voltage >= self.data.ov_protection:
 63                raise OverVoltageError(
 64                    f"Overvoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}. "
 65                    f"Voltage {new_voltage} is higher than {self.data.ov_protection}."
 66                )
 67
 68            self._resource.write(f"CHAR{self.id}:VOLT {new_voltage}")
 69            self._volts = new_voltage
 70
 71    @property
 72    def ohms(self) -> float:
 73        """Measures internal resistance of the cell"""
 74        with self._lock:
 75            return self._resistance  #  MEAS1:R?
 76
 77    @ohms.setter
 78    def ohms(self, new_ohms: float):
 79        """Sets the internal resistance of the cell."""
 80        with self._lock:
 81            self._resistance = new_ohms
 82            self._resource.write(f"CHAR{self.id}:R {new_ohms}")
 83
 84    @property
 85    def amps(self) -> float:
 86        """Measures cell current."""
 87        with self._lock:
 88            result = float(self._resource.query(f"MEAS{self.id}:CURR?"))
 89            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 90
 91    @amps.setter
 92    def amps(self, new_amps: float):
 93        """Sets the current limit of the cell."""
 94        with self._lock:
 95            self._resource.write(f"CHAR{self.id}:OUTCURR {new_amps}")
 96
 97    @property
 98    def compensation_mode(self) -> CellCompMode:
 99        """Sets compensation mode"""
100        return CellCompMode.LLOCAL
101
102    @compensation_mode.setter
103    def compensation_mode(self, mode: CellCompMode):
104        """Sets compensation mode"""
105
106    def enable(self):
107        """Enables the cell."""
108        self._resource.write(f"OUTP{self.id}:ONOFF 1")
109        self._thread_pause_flag.set()
110
111    def disable(self):
112        """Disables the cell."""
113        self._thread_pause_flag.clear()
114        self._resource.write(f"OUTP{self.id}:ONOFF 0")
115
116    def reset(self):
117        """Resets the instrument"""
118        self._resource.write("*RST")
119        self._resource.write(  # Set all channels to charge/discharge mode
120            f"OUTP:MODE {NGIMode.CHARGE}(@1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)"
121        )
122        time.sleep(6)  # Needs delay after reset

NGI N83624 Cell Simulator command wrapper.

measured_volts: float
40    @property
41    def measured_volts(self) -> float:
42        """Measures actual cell voltage."""
43        with self._lock:
44            result = float(self._resource.query(f"MEAS{self.id}:VOLT?"))
45            return result if result != 9.91e37 else 0.0  # Convert NaN to 0

Measures actual cell voltage.

volts: float
47    @property
48    def volts(self) -> float:
49        """Gets the last target voltage."""
50        with self._lock:
51            return self._volts

Gets the last target voltage.

ohms: float
71    @property
72    def ohms(self) -> float:
73        """Measures internal resistance of the cell"""
74        with self._lock:
75            return self._resistance  #  MEAS1:R?

Measures internal resistance of the cell

amps: float
84    @property
85    def amps(self) -> float:
86        """Measures cell current."""
87        with self._lock:
88            result = float(self._resource.query(f"MEAS{self.id}:CURR?"))
89            return result if result != 9.91e37 else 0.0  # Convert NaN to 0

Measures cell current.

compensation_mode: hitl_tester.modules.bms_types.CellCompMode
 97    @property
 98    def compensation_mode(self) -> CellCompMode:
 99        """Sets compensation mode"""
100        return CellCompMode.LLOCAL

Sets compensation mode

def enable(self):
106    def enable(self):
107        """Enables the cell."""
108        self._resource.write(f"OUTP{self.id}:ONOFF 1")
109        self._thread_pause_flag.set()

Enables the cell.

def disable(self):
111    def disable(self):
112        """Disables the cell."""
113        self._thread_pause_flag.clear()
114        self._resource.write(f"OUTP{self.id}:ONOFF 0")

Disables the cell.

def reset(self):
116    def reset(self):
117        """Resets the instrument"""
118        self._resource.write("*RST")
119        self._resource.write(  # Set all channels to charge/discharge mode
120            f"OUTP:MODE {NGIMode.CHARGE}(@1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)"
121        )
122        time.sleep(6)  # Needs delay after reset

Resets the instrument