hitl_tester.modules.bms.plateset
Provides controls for the plateset.
(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 plateset. 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 30from __future__ import annotations 31 32import atexit 33import math 34import time 35from enum import Enum 36 37import pytest 38from typing_extensions import Self 39 40from hitl_tester.modules.bms_types import BMSFlags, SuppressExceptions 41from hitl_tester.modules.file_lock import FileLock, RFileLock 42from hitl_tester.modules.logger import logger 43 44if hasattr(pytest, "flags") and isinstance(pytest.flags, BMSFlags) and pytest.flags.dry_run: 45 from hitl_tester.modules.bms.pseudo_hardware import DAQC2 46else: 47 import piplates.DAQC2plate as DAQC2 # type: ignore[no-redef] 48 49 50class ThermistorProperties(float, Enum): 51 """Values that define how the thermistor operates. Provided by manufacturer.""" 52 53 A = 0.0001 54 B = -0.043 55 C = -0.1 56 D = 30 57 VREF = 3 58 59 60class Plateset: 61 """Controls for the plateset.""" 62 63 _thermistor1: float = 0 64 _thermistor2: float = 0 65 _ce_switch: bool = False 66 _load_switch: bool = False 67 _charger_switch: bool = False 68 _instance: Self | None = None 69 _lock = RFileLock("piplate") 70 _board_id = 0 71 disengage_safety_protocols: bool = False 72 73 def __new__(cls): 74 """Make Plateset a singleton""" 75 if cls._instance is None: 76 cls._instance = super().__new__(cls) 77 78 @atexit.register 79 def __atexit__(): 80 """Configure a safe shut down for when the class instance is destroyed.""" 81 logger.write_info_to_report("Powering off plateset CE switch") 82 cls._instance.ce_switch = False 83 84 return cls._instance 85 86 def __init__(self): 87 if hasattr(pytest, "flags") and isinstance(pytest.flags, BMSFlags): 88 configs = pytest.flags.config.get("daqc2_board", {}) 89 self.thermistor1_dac_channel = configs.get("thermistor1_dac_channel", 1) 90 self.thermistor2_dac_channel = configs.get("thermistor2_dac_channel", 0) 91 self.ce_switch_dout_bit = configs.get("ce_switch_dout_bit", 7) 92 self.load_switch_dout_bit = configs.get("load_switch_dout_bit", 6) 93 self.charger_switch_dout_bit = configs.get("charger_switch_dout_bit", 5) 94 self.temperature_adc_channel: int | None = configs.get("temperature_adc_channel") 95 self._board_id = configs.get("board_id", 2) 96 self._sem = FileLock("CE") 97 98 def set_thermistor(self, address: int, channel: int, celsius: float): 99 """Performs the calculations need to set the thermistor to some temperature.""" 100 if self.disengage_safety_protocols or -40 <= celsius <= 90: 101 r = ( 102 ThermistorProperties.D 103 * math.e 104 ** (ThermistorProperties.A * (celsius**2) + ThermistorProperties.B * celsius + ThermistorProperties.C) 105 * 1000 106 ) 107 vdac = ThermistorProperties.VREF * (r / (10000 + r)) 108 with SuppressExceptions(), self._lock: 109 DAQC2.setDAC(address, channel, vdac) # (address, channel, temp C converted to Volts) 110 else: 111 raise ValueError("Temperature range must be within -40C and 90C") 112 113 @property 114 def thermistor1(self) -> float: 115 """Gets the temperature (in Celsius) of thermistor 1.""" 116 return Plateset._thermistor1 117 118 @thermistor1.setter 119 def thermistor1(self, celsius: float): 120 """Sets the temperature (in Celsius) of thermistor 1.""" 121 self.set_thermistor(self._board_id, self.thermistor1_dac_channel, celsius) 122 Plateset._thermistor1 = celsius 123 124 @property 125 def thermistor2(self) -> float: 126 """Gets the temperature (in Celsius) of thermistor 2.""" 127 return Plateset._thermistor2 128 129 @thermistor2.setter 130 def thermistor2(self, celsius: float): 131 """Sets the temperature (in Celsius) of thermistor 2.""" 132 self.set_thermistor(self._board_id, self.thermistor2_dac_channel, celsius) 133 Plateset._thermistor2 = celsius 134 135 @property 136 def ce_switch(self) -> bool: 137 """Returns the last setting for the CE switch on the plate set""" 138 return Plateset._ce_switch 139 140 @ce_switch.setter 141 def ce_switch(self, enable: bool): 142 """Enables/Disables (turns on/off) the CE switch on the plate set.""" 143 with SuppressExceptions(), self._lock: 144 Plateset._ce_switch = enable 145 if enable: 146 self._sem.acquire(shared=True) 147 DAQC2.setDOUTbit(self._board_id, self.ce_switch_dout_bit) # Allow charging 148 else: 149 if self._sem.acquire(blocking=False): # Are we the last reference? 150 DAQC2.clrDOUTbit(self._board_id, self.ce_switch_dout_bit) # Disallow charging 151 self._sem.release() 152 time.sleep(1) 153 154 @property 155 def load_switch(self) -> bool: 156 """Returns the last setting for the load switch on the plate set""" 157 return Plateset._load_switch 158 159 @load_switch.setter 160 def load_switch(self, enable: bool): 161 """Enables/Disables (turns on/off) the load switch on the plate set.""" 162 with SuppressExceptions(), self._lock: 163 Plateset._load_switch = enable 164 if enable: 165 DAQC2.setDOUTbit(self._board_id, self.load_switch_dout_bit) # (addr, bit) 166 else: 167 DAQC2.clrDOUTbit(self._board_id, self.load_switch_dout_bit) # (addr, bit) 168 169 @property 170 def charger_switch(self) -> bool: 171 """Returns the last setting for the charger switch on the plate set""" 172 return Plateset._charger_switch 173 174 @charger_switch.setter 175 def charger_switch(self, enable: bool): 176 """Enables/Disables (turns on/off) the charger switch on the plate set.""" 177 with SuppressExceptions(), self._lock: 178 Plateset._charger_switch = enable 179 if enable: 180 DAQC2.setDOUTbit(self._board_id, self.charger_switch_dout_bit) # (addr, bit) 181 else: 182 DAQC2.clrDOUTbit(self._board_id, self.charger_switch_dout_bit) # (addr, bit) 183 184 @property 185 def temperature(self) -> float: 186 """Returns the temperature reading in Celsius from the plate set.""" 187 if self.temperature_adc_channel is not None: 188 with SuppressExceptions(), self._lock: 189 try: 190 return float(DAQC2.getADC(self._board_id, self.temperature_adc_channel) * 100) 191 except IndexError: 192 logger.write_debug_to_report("DAQC2 temperature error: no response") 193 return 0.0
class
ThermistorProperties(builtins.float, enum.Enum):
51class ThermistorProperties(float, Enum): 52 """Values that define how the thermistor operates. Provided by manufacturer.""" 53 54 A = 0.0001 55 B = -0.043 56 C = -0.1 57 D = 30 58 VREF = 3
Values that define how the thermistor operates. Provided by manufacturer.
A =
<ThermistorProperties.A: 0.0001>
B =
<ThermistorProperties.B: -0.043>
C =
<ThermistorProperties.C: -0.1>
D =
<ThermistorProperties.D: 30.0>
VREF =
<ThermistorProperties.VREF: 3.0>
Inherited Members
- enum.Enum
- name
- value
- builtins.float
- conjugate
- as_integer_ratio
- fromhex
- hex
- is_integer
- real
- imag
class
Plateset:
61class Plateset: 62 """Controls for the plateset.""" 63 64 _thermistor1: float = 0 65 _thermistor2: float = 0 66 _ce_switch: bool = False 67 _load_switch: bool = False 68 _charger_switch: bool = False 69 _instance: Self | None = None 70 _lock = RFileLock("piplate") 71 _board_id = 0 72 disengage_safety_protocols: bool = False 73 74 def __new__(cls): 75 """Make Plateset a singleton""" 76 if cls._instance is None: 77 cls._instance = super().__new__(cls) 78 79 @atexit.register 80 def __atexit__(): 81 """Configure a safe shut down for when the class instance is destroyed.""" 82 logger.write_info_to_report("Powering off plateset CE switch") 83 cls._instance.ce_switch = False 84 85 return cls._instance 86 87 def __init__(self): 88 if hasattr(pytest, "flags") and isinstance(pytest.flags, BMSFlags): 89 configs = pytest.flags.config.get("daqc2_board", {}) 90 self.thermistor1_dac_channel = configs.get("thermistor1_dac_channel", 1) 91 self.thermistor2_dac_channel = configs.get("thermistor2_dac_channel", 0) 92 self.ce_switch_dout_bit = configs.get("ce_switch_dout_bit", 7) 93 self.load_switch_dout_bit = configs.get("load_switch_dout_bit", 6) 94 self.charger_switch_dout_bit = configs.get("charger_switch_dout_bit", 5) 95 self.temperature_adc_channel: int | None = configs.get("temperature_adc_channel") 96 self._board_id = configs.get("board_id", 2) 97 self._sem = FileLock("CE") 98 99 def set_thermistor(self, address: int, channel: int, celsius: float): 100 """Performs the calculations need to set the thermistor to some temperature.""" 101 if self.disengage_safety_protocols or -40 <= celsius <= 90: 102 r = ( 103 ThermistorProperties.D 104 * math.e 105 ** (ThermistorProperties.A * (celsius**2) + ThermistorProperties.B * celsius + ThermistorProperties.C) 106 * 1000 107 ) 108 vdac = ThermistorProperties.VREF * (r / (10000 + r)) 109 with SuppressExceptions(), self._lock: 110 DAQC2.setDAC(address, channel, vdac) # (address, channel, temp C converted to Volts) 111 else: 112 raise ValueError("Temperature range must be within -40C and 90C") 113 114 @property 115 def thermistor1(self) -> float: 116 """Gets the temperature (in Celsius) of thermistor 1.""" 117 return Plateset._thermistor1 118 119 @thermistor1.setter 120 def thermistor1(self, celsius: float): 121 """Sets the temperature (in Celsius) of thermistor 1.""" 122 self.set_thermistor(self._board_id, self.thermistor1_dac_channel, celsius) 123 Plateset._thermistor1 = celsius 124 125 @property 126 def thermistor2(self) -> float: 127 """Gets the temperature (in Celsius) of thermistor 2.""" 128 return Plateset._thermistor2 129 130 @thermistor2.setter 131 def thermistor2(self, celsius: float): 132 """Sets the temperature (in Celsius) of thermistor 2.""" 133 self.set_thermistor(self._board_id, self.thermistor2_dac_channel, celsius) 134 Plateset._thermistor2 = celsius 135 136 @property 137 def ce_switch(self) -> bool: 138 """Returns the last setting for the CE switch on the plate set""" 139 return Plateset._ce_switch 140 141 @ce_switch.setter 142 def ce_switch(self, enable: bool): 143 """Enables/Disables (turns on/off) the CE switch on the plate set.""" 144 with SuppressExceptions(), self._lock: 145 Plateset._ce_switch = enable 146 if enable: 147 self._sem.acquire(shared=True) 148 DAQC2.setDOUTbit(self._board_id, self.ce_switch_dout_bit) # Allow charging 149 else: 150 if self._sem.acquire(blocking=False): # Are we the last reference? 151 DAQC2.clrDOUTbit(self._board_id, self.ce_switch_dout_bit) # Disallow charging 152 self._sem.release() 153 time.sleep(1) 154 155 @property 156 def load_switch(self) -> bool: 157 """Returns the last setting for the load switch on the plate set""" 158 return Plateset._load_switch 159 160 @load_switch.setter 161 def load_switch(self, enable: bool): 162 """Enables/Disables (turns on/off) the load switch on the plate set.""" 163 with SuppressExceptions(), self._lock: 164 Plateset._load_switch = enable 165 if enable: 166 DAQC2.setDOUTbit(self._board_id, self.load_switch_dout_bit) # (addr, bit) 167 else: 168 DAQC2.clrDOUTbit(self._board_id, self.load_switch_dout_bit) # (addr, bit) 169 170 @property 171 def charger_switch(self) -> bool: 172 """Returns the last setting for the charger switch on the plate set""" 173 return Plateset._charger_switch 174 175 @charger_switch.setter 176 def charger_switch(self, enable: bool): 177 """Enables/Disables (turns on/off) the charger switch on the plate set.""" 178 with SuppressExceptions(), self._lock: 179 Plateset._charger_switch = enable 180 if enable: 181 DAQC2.setDOUTbit(self._board_id, self.charger_switch_dout_bit) # (addr, bit) 182 else: 183 DAQC2.clrDOUTbit(self._board_id, self.charger_switch_dout_bit) # (addr, bit) 184 185 @property 186 def temperature(self) -> float: 187 """Returns the temperature reading in Celsius from the plate set.""" 188 if self.temperature_adc_channel is not None: 189 with SuppressExceptions(), self._lock: 190 try: 191 return float(DAQC2.getADC(self._board_id, self.temperature_adc_channel) * 100) 192 except IndexError: 193 logger.write_debug_to_report("DAQC2 temperature error: no response") 194 return 0.0
Controls for the plateset.
Plateset()
74 def __new__(cls): 75 """Make Plateset a singleton""" 76 if cls._instance is None: 77 cls._instance = super().__new__(cls) 78 79 @atexit.register 80 def __atexit__(): 81 """Configure a safe shut down for when the class instance is destroyed.""" 82 logger.write_info_to_report("Powering off plateset CE switch") 83 cls._instance.ce_switch = False 84 85 return cls._instance
Make Plateset a singleton
def
set_thermistor(self, address: int, channel: int, celsius: float):
99 def set_thermistor(self, address: int, channel: int, celsius: float): 100 """Performs the calculations need to set the thermistor to some temperature.""" 101 if self.disengage_safety_protocols or -40 <= celsius <= 90: 102 r = ( 103 ThermistorProperties.D 104 * math.e 105 ** (ThermistorProperties.A * (celsius**2) + ThermistorProperties.B * celsius + ThermistorProperties.C) 106 * 1000 107 ) 108 vdac = ThermistorProperties.VREF * (r / (10000 + r)) 109 with SuppressExceptions(), self._lock: 110 DAQC2.setDAC(address, channel, vdac) # (address, channel, temp C converted to Volts) 111 else: 112 raise ValueError("Temperature range must be within -40C and 90C")
Performs the calculations need to set the thermistor to some temperature.
thermistor1: float
114 @property 115 def thermistor1(self) -> float: 116 """Gets the temperature (in Celsius) of thermistor 1.""" 117 return Plateset._thermistor1
Gets the temperature (in Celsius) of thermistor 1.
thermistor2: float
125 @property 126 def thermistor2(self) -> float: 127 """Gets the temperature (in Celsius) of thermistor 2.""" 128 return Plateset._thermistor2
Gets the temperature (in Celsius) of thermistor 2.
ce_switch: bool
136 @property 137 def ce_switch(self) -> bool: 138 """Returns the last setting for the CE switch on the plate set""" 139 return Plateset._ce_switch
Returns the last setting for the CE switch on the plate set
load_switch: bool
155 @property 156 def load_switch(self) -> bool: 157 """Returns the last setting for the load switch on the plate set""" 158 return Plateset._load_switch
Returns the last setting for the load switch on the plate set
charger_switch: bool
170 @property 171 def charger_switch(self) -> bool: 172 """Returns the last setting for the charger switch on the plate set""" 173 return Plateset._charger_switch
Returns the last setting for the charger switch on the plate set
temperature: float
185 @property 186 def temperature(self) -> float: 187 """Returns the temperature reading in Celsius from the plate set.""" 188 if self.temperature_adc_channel is not None: 189 with SuppressExceptions(), self._lock: 190 try: 191 return float(DAQC2.getADC(self._board_id, self.temperature_adc_channel) * 100) 192 except IndexError: 193 logger.write_debug_to_report("DAQC2 temperature error: no response") 194 return 0.0
Returns the temperature reading in Celsius from the plate set.