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>
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

disengage_safety_protocols: bool = False
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.