hitl_tester.modules.bms_types
Various types shared between modules.
(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""" 2Various types shared between modules. 3 4(c) 2020-2024 TurnAround Factor, Inc. 5 6CUI DISTRIBUTION CONTROL 7Controlled by: DLA J68 R&D SBIP 8CUI Category: Small Business Research and Technology 9Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS 10POC: GOV SBIP Program Manager Denise Price, 571-767-0111 11Distribution authorized to U.S. Government Agencies only, to protect information not owned by the 12U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that 13it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests 14for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, 15Fort Belvoir, VA 22060-6221 16 17SBIR DATA RIGHTS 18Contract No.:SP4701-23-C-0083 19Contractor Name: TurnAround Factor, Inc. 20Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 21Expiration of SBIR Data Rights Period: September 24, 2029 22The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer 23software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights 24in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause 25contained in the above identified contract. No restrictions apply after the expiration date shown above. Any 26reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce 27the markings. 28""" 29 30from __future__ import annotations 31 32import inspect 33import time 34import traceback 35from dataclasses import dataclass 36from enum import Enum, auto, IntFlag, IntEnum 37from pathlib import Path 38from types import TracebackType 39from typing import Protocol, runtime_checkable, Any, Type 40 41import _pytest 42 43try: 44 from pyvisa.constants import StatusCode 45except ModuleNotFoundError: 46 pass # Allow hitl_tester to import bms_types 47 48try: 49 from hitl_tester.modules.logger import logger 50 from hitl_tester.modules.file_lock import ResourceLock 51except ModuleNotFoundError: 52 pass # Allow hitl_tester to import bms_types 53 54 55# Test Hardware Type Definitions 56@dataclass 57class DMMImpedance: 58 """Impedance config for DMM.""" 59 60 pulse_current: int 61 total_readings: int # Used for averaging results 62 63 64class BatteryType(Enum): 65 """Supported battery types.""" 66 67 UNDEFINED = auto() 68 NICD = auto() 69 LI = auto() 70 71 72class NiCdChargeCycle(Enum): 73 """Supported NiCad charge cycle types.""" 74 75 UNDEFINED = 0 76 STANDARD = 1 # 0.1C for 16 hours 77 CUSTOM = 2 78 DV_DT = 3 # C/2 for 2.5 hours 79 80 81class DischargeType(Enum): 82 """Supported discharge types.""" 83 84 UNDEFINED = 0 85 CONSTANT_CURRENT = 1 86 CONSTANT_RESISTANCE = 2 87 CONSTANT_VOLTAGE = 3 88 89 90# Other 91class StopWatch: 92 """Record time with support for suspension.""" 93 94 def __init__(self) -> None: 95 self._start_time: float = time.perf_counter() 96 self._suspend_time: float | None = None 97 98 @property 99 def elapsed_time(self) -> float: 100 """How much time has passed since reset().""" 101 if self._suspend_time is None: 102 return time.perf_counter() - self._start_time 103 return self._suspend_time - self._start_time 104 105 def reset(self) -> None: 106 """Reset the timer to 0.""" 107 self._start_time = time.perf_counter() 108 109 def start(self) -> None: 110 """Start the timer.""" 111 if self._suspend_time is not None: 112 self._start_time += time.perf_counter() - self._suspend_time 113 self._suspend_time = None 114 115 def stop(self) -> None: 116 """Stop the timer.""" 117 self._suspend_time = time.perf_counter() 118 119 120# Enums 121class ControlStatusRegister(IntFlag): 122 """Reset flags from the CSR.""" 123 124 LOW_POWER = 0x80000000 125 WINDOW_WATCHDOG = 0x40000000 126 INDEPENDANT_WATCHDOG = 0x20000000 127 SOFTWARE = 0x10000000 128 POWER = 0x08000000 129 RESET_PIN = 0x04000000 130 OPTION_BYTE_LOADER = 0x02000000 131 132 def __str__(self) -> str: 133 return " | ".join(str(flag.name).title() for flag in ControlStatusRegister if self.value & flag) 134 135 136class CellState(IntEnum): 137 """The current state of a cell.""" 138 139 UNKNOWN = 0 140 RESTING = 1 141 CHARGING_EXCITED = 2 142 DISCHARGING_EXCITED = 3 143 CHARGING_TIMER = 4 144 DISCHARGING_TIMER = 5 145 146 def __str__(self) -> str: 147 return self.name.title().replace("_", " ") 148 149 @classmethod 150 def _missing_(cls, _: object): 151 return cls(cls.UNKNOWN) 152 153 154class BMSState(IntEnum): 155 """The current state of the BMS.""" 156 157 UNKNOWN = 0 158 SLOW_SAMPLE = 1 159 FAST_SAMPLE = 2 160 DEEP_SLUMBER = 3 161 CALIBRATION = 4 162 PREFAULT = 5 163 FAULT = 6 164 FAULT_SLUMBER = 7 165 FAULT_PERMANENT_DISABLE = 8 166 167 def __str__(self) -> str: 168 return self.name.title().replace("_", " ") 169 170 @classmethod 171 def _missing_(cls, _: object): 172 return cls(cls.UNKNOWN) 173 174 175# BMS HW Enums 176class CellCompMode(Enum): 177 """ 178 This circuit compensates the output of the dc source according to the input capacitance of the device being tested 179 as well as the type of output connections being used. 180 """ 181 182 HREMOTE = auto() # (DEFAULT) Used for faster response with long load leads using remote sensing. 183 HLOCAL = auto() # Use for faster response with short load leads or bench operation (no external cap needed). 184 LREMOTE = auto() # Used for slower response with long load leads using remote sensing. 185 LLOCAL = auto() # Used for slower response with short load leads or bench operation. 186 187 188class NGIMode(Enum): 189 """ 190 This circuit compensates the output of the dc source according to the input capacitance of the device being tested 191 as well as the type of output connections being used. 192 """ 193 194 SOURCE = 0 # N83624 source mode includes constant voltage. 195 CHARGE = 1 # Under Charge mode, battery charging and discharging. 196 SOC = 2 # The SOC function simulates battery charging. 197 SEQ = 3 # Executes sequentially according to the output parameters of a step file. 198 199 200# Exceptions 201class LoggingError(Exception): 202 """Log the exception before raising it.""" 203 204 def __init__(self, message): 205 """Output error message and raise exception.""" 206 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 207 super().__init__(message) 208 209 210class PacketError(LoggingError): 211 """Raised when a packet in malformed.""" 212 213 214class ResourceNotFoundError(LoggingError): 215 """Raised when a hardware resource is not found.""" 216 217 218class OverTemperatureError(LoggingError): 219 """Raised when an over-temperature is triggered.""" 220 221 222class OverVoltageError(LoggingError): 223 """Raised when an overvoltage is triggered.""" 224 225 226class UnderVoltageError(LoggingError): 227 """Raised when an undervoltage is triggered.""" 228 229 230class TimeoutExceededError(LoggingError): 231 """Some event did not occur in the specified amount of time.""" 232 233 234class VisaSystemError(LoggingError): 235 """The instrument reported a system error.""" 236 237 238class ValueLogError(LoggingError): 239 """A value error that gets logged.""" 240 241 242class ChargerOrLoad(Protocol): 243 """Protocol for a charger or load object""" 244 245 def enable(self): 246 """Enables instrument output""" 247 248 def disable(self): 249 """Disables instrument output""" 250 251 def reset(self): 252 """Resets the instrument""" 253 254 255class SuppressExceptions: 256 """Suppresses all exceptions.""" 257 258 def __enter__(self): 259 """NOP""" 260 261 def __exit__( 262 self, exc_type: Type[BaseException] | None, exc_value: BaseException | None, exc_traceback: TracebackType | None 263 ) -> bool: 264 """Suppress all exceptions by returning True.""" 265 whitelist = [None, _pytest.outcomes.Exit, KeyboardInterrupt, SystemExit] 266 if result := exc_type not in whitelist: 267 exception_message = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) 268 logger.write_debug_to_report(exception_message.rstrip("\n")) 269 return result 270 271 272class SafeResource: 273 """Gives multiprocess-safe access to Pyvisa resources without raising exceptions.""" 274 275 def __init__(self, resource_address: str | tuple[str, str, str]): 276 self.resource_address = resource_address 277 self.lock = ResourceLock(resource_address) 278 self.default_result = "0" 279 280 def log_caller(self): 281 """Log the calling function.""" 282 caller = ( 283 f"File {inspect.stack()[2].filename}, line {inspect.stack()[2].lineno}, " 284 f"in {inspect.stack()[2].function}\n {(''.join(inspect.stack()[2].code_context or [])).strip(chr(10))}" 285 ) 286 logger.write_debug_to_report(caller) 287 288 @property 289 def baud_rate(self) -> int: 290 """The baud rate for the serial connection.""" 291 return int(self.lock.baud_rate) 292 293 @baud_rate.setter 294 def baud_rate(self, new_baud_rate: int): 295 self.lock.baud_rate = new_baud_rate 296 297 @property 298 def last_status(self) -> StatusCode: 299 """PyVISA status code suppressing exceptions.""" 300 with SuppressExceptions(), self.lock: 301 assert self.lock.resource 302 return self.lock.resource.last_status 303 return StatusCode.success 304 305 @property 306 def timeout(self) -> float: 307 """Get PyVISA timeout value.""" 308 with SuppressExceptions(), self.lock: 309 assert self.lock.resource 310 return self.lock.resource.timeout 311 return 0.0 312 313 @timeout.setter 314 def timeout(self, new_timeout: float): 315 """Set PyVISA timeout value.""" 316 with SuppressExceptions(), self.lock: 317 assert self.lock.resource 318 self.lock.resource.timeout = new_timeout 319 320 def write(self, command: str): 321 """PyVISA write suppressing exceptions.""" 322 with SuppressExceptions(), self.lock: 323 assert self.lock.resource 324 self.lock.resource.write(command) 325 return 326 self.log_caller() 327 328 def query(self, command: str) -> str: 329 """PyVISA query suppressing exceptions.""" 330 with SuppressExceptions(), self.lock: 331 assert self.lock.resource 332 return self.lock.resource.query(command) 333 self.log_caller() 334 return self.default_result 335 336 def read_raw(self) -> bytes: 337 """PyVISA read_raw suppressing exceptions.""" 338 with SuppressExceptions(), self.lock: 339 assert self.lock.resource 340 return self.lock.resource.read_raw() 341 self.log_caller() 342 return b"" 343 344 345# Flags 346@runtime_checkable 347class BMSFlags(Protocol): 348 """The flags passed from hitl_tester to bms_hw.""" 349 350 plateset_id: str = "" 351 dry_run: bool = False 352 doc_generation: bool = False 353 cell_chemistry: str = "" 354 config: dict[str, str | dict[str, Any]] = {} # FIXME(JA): break out into separate attributes with sane defaults 355 report_filename: Path = Path() 356 properties: dict[str, Any] | None = {} 357 test_plan: Path = Path() 358 359 360class TestFlags(BMSFlags): 361 """The flags to be passed from hitl_tester to bms_hw.""" 362 363 def __init__( 364 self, 365 dry_run: bool = False, 366 doc_generation: bool = False, 367 cell_chemistry: str = "", 368 properties: dict[str, Any] | None = None, 369 test_plan: Path = Path(), 370 ): 371 self.dry_run = dry_run 372 self.doc_generation = doc_generation 373 self.cell_chemistry = cell_chemistry 374 self.properties = properties or {} 375 self.config = {} 376 self.test_plan = test_plan
57@dataclass 58class DMMImpedance: 59 """Impedance config for DMM.""" 60 61 pulse_current: int 62 total_readings: int # Used for averaging results
Impedance config for DMM.
65class BatteryType(Enum): 66 """Supported battery types.""" 67 68 UNDEFINED = auto() 69 NICD = auto() 70 LI = auto()
Supported battery types.
Inherited Members
- enum.Enum
- name
- value
73class NiCdChargeCycle(Enum): 74 """Supported NiCad charge cycle types.""" 75 76 UNDEFINED = 0 77 STANDARD = 1 # 0.1C for 16 hours 78 CUSTOM = 2 79 DV_DT = 3 # C/2 for 2.5 hours
Supported NiCad charge cycle types.
Inherited Members
- enum.Enum
- name
- value
82class DischargeType(Enum): 83 """Supported discharge types.""" 84 85 UNDEFINED = 0 86 CONSTANT_CURRENT = 1 87 CONSTANT_RESISTANCE = 2 88 CONSTANT_VOLTAGE = 3
Supported discharge types.
Inherited Members
- enum.Enum
- name
- value
92class StopWatch: 93 """Record time with support for suspension.""" 94 95 def __init__(self) -> None: 96 self._start_time: float = time.perf_counter() 97 self._suspend_time: float | None = None 98 99 @property 100 def elapsed_time(self) -> float: 101 """How much time has passed since reset().""" 102 if self._suspend_time is None: 103 return time.perf_counter() - self._start_time 104 return self._suspend_time - self._start_time 105 106 def reset(self) -> None: 107 """Reset the timer to 0.""" 108 self._start_time = time.perf_counter() 109 110 def start(self) -> None: 111 """Start the timer.""" 112 if self._suspend_time is not None: 113 self._start_time += time.perf_counter() - self._suspend_time 114 self._suspend_time = None 115 116 def stop(self) -> None: 117 """Stop the timer.""" 118 self._suspend_time = time.perf_counter()
Record time with support for suspension.
99 @property 100 def elapsed_time(self) -> float: 101 """How much time has passed since reset().""" 102 if self._suspend_time is None: 103 return time.perf_counter() - self._start_time 104 return self._suspend_time - self._start_time
How much time has passed since reset().
106 def reset(self) -> None: 107 """Reset the timer to 0.""" 108 self._start_time = time.perf_counter()
Reset the timer to 0.
122class ControlStatusRegister(IntFlag): 123 """Reset flags from the CSR.""" 124 125 LOW_POWER = 0x80000000 126 WINDOW_WATCHDOG = 0x40000000 127 INDEPENDANT_WATCHDOG = 0x20000000 128 SOFTWARE = 0x10000000 129 POWER = 0x08000000 130 RESET_PIN = 0x04000000 131 OPTION_BYTE_LOADER = 0x02000000 132 133 def __str__(self) -> str: 134 return " | ".join(str(flag.name).title() for flag in ControlStatusRegister if self.value & flag)
Reset flags from the CSR.
Inherited Members
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
- enum.Enum
- name
- value
137class CellState(IntEnum): 138 """The current state of a cell.""" 139 140 UNKNOWN = 0 141 RESTING = 1 142 CHARGING_EXCITED = 2 143 DISCHARGING_EXCITED = 3 144 CHARGING_TIMER = 4 145 DISCHARGING_TIMER = 5 146 147 def __str__(self) -> str: 148 return self.name.title().replace("_", " ") 149 150 @classmethod 151 def _missing_(cls, _: object): 152 return cls(cls.UNKNOWN)
The current state of a cell.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
155class BMSState(IntEnum): 156 """The current state of the BMS.""" 157 158 UNKNOWN = 0 159 SLOW_SAMPLE = 1 160 FAST_SAMPLE = 2 161 DEEP_SLUMBER = 3 162 CALIBRATION = 4 163 PREFAULT = 5 164 FAULT = 6 165 FAULT_SLUMBER = 7 166 FAULT_PERMANENT_DISABLE = 8 167 168 def __str__(self) -> str: 169 return self.name.title().replace("_", " ") 170 171 @classmethod 172 def _missing_(cls, _: object): 173 return cls(cls.UNKNOWN)
The current state of the BMS.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
177class CellCompMode(Enum): 178 """ 179 This circuit compensates the output of the dc source according to the input capacitance of the device being tested 180 as well as the type of output connections being used. 181 """ 182 183 HREMOTE = auto() # (DEFAULT) Used for faster response with long load leads using remote sensing. 184 HLOCAL = auto() # Use for faster response with short load leads or bench operation (no external cap needed). 185 LREMOTE = auto() # Used for slower response with long load leads using remote sensing. 186 LLOCAL = auto() # Used for slower response with short load leads or bench operation.
This circuit compensates the output of the dc source according to the input capacitance of the device being tested as well as the type of output connections being used.
Inherited Members
- enum.Enum
- name
- value
189class NGIMode(Enum): 190 """ 191 This circuit compensates the output of the dc source according to the input capacitance of the device being tested 192 as well as the type of output connections being used. 193 """ 194 195 SOURCE = 0 # N83624 source mode includes constant voltage. 196 CHARGE = 1 # Under Charge mode, battery charging and discharging. 197 SOC = 2 # The SOC function simulates battery charging. 198 SEQ = 3 # Executes sequentially according to the output parameters of a step file.
This circuit compensates the output of the dc source according to the input capacitance of the device being tested as well as the type of output connections being used.
Inherited Members
- enum.Enum
- name
- value
202class LoggingError(Exception): 203 """Log the exception before raising it.""" 204 205 def __init__(self, message): 206 """Output error message and raise exception.""" 207 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 208 super().__init__(message)
Log the exception before raising it.
205 def __init__(self, message): 206 """Output error message and raise exception.""" 207 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 208 super().__init__(message)
Output error message and raise exception.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
Raised when a packet in malformed.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
215class ResourceNotFoundError(LoggingError): 216 """Raised when a hardware resource is not found."""
Raised when a hardware resource is not found.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
219class OverTemperatureError(LoggingError): 220 """Raised when an over-temperature is triggered."""
Raised when an over-temperature is triggered.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
Raised when an overvoltage is triggered.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
Raised when an undervoltage is triggered.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
231class TimeoutExceededError(LoggingError): 232 """Some event did not occur in the specified amount of time."""
Some event did not occur in the specified amount of time.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
The instrument reported a system error.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
A value error that gets logged.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
243class ChargerOrLoad(Protocol): 244 """Protocol for a charger or load object""" 245 246 def enable(self): 247 """Enables instrument output""" 248 249 def disable(self): 250 """Disables instrument output""" 251 252 def reset(self): 253 """Resets the instrument"""
Protocol for a charger or load object
1767def _no_init_or_replace_init(self, *args, **kwargs): 1768 cls = type(self) 1769 1770 if cls._is_protocol: 1771 raise TypeError('Protocols cannot be instantiated') 1772 1773 # Already using a custom `__init__`. No need to calculate correct 1774 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1775 if cls.__init__ is not _no_init_or_replace_init: 1776 return 1777 1778 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1779 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1780 # searches for a proper new `__init__` in the MRO. The new `__init__` 1781 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1782 # instantiation of the protocol subclass will thus use the new 1783 # `__init__` and no longer call `_no_init_or_replace_init`. 1784 for base in cls.__mro__: 1785 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1786 if init is not _no_init_or_replace_init: 1787 cls.__init__ = init 1788 break 1789 else: 1790 # should not happen 1791 cls.__init__ = object.__init__ 1792 1793 cls.__init__(self, *args, **kwargs)
256class SuppressExceptions: 257 """Suppresses all exceptions.""" 258 259 def __enter__(self): 260 """NOP""" 261 262 def __exit__( 263 self, exc_type: Type[BaseException] | None, exc_value: BaseException | None, exc_traceback: TracebackType | None 264 ) -> bool: 265 """Suppress all exceptions by returning True.""" 266 whitelist = [None, _pytest.outcomes.Exit, KeyboardInterrupt, SystemExit] 267 if result := exc_type not in whitelist: 268 exception_message = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) 269 logger.write_debug_to_report(exception_message.rstrip("\n")) 270 return result
Suppresses all exceptions.
273class SafeResource: 274 """Gives multiprocess-safe access to Pyvisa resources without raising exceptions.""" 275 276 def __init__(self, resource_address: str | tuple[str, str, str]): 277 self.resource_address = resource_address 278 self.lock = ResourceLock(resource_address) 279 self.default_result = "0" 280 281 def log_caller(self): 282 """Log the calling function.""" 283 caller = ( 284 f"File {inspect.stack()[2].filename}, line {inspect.stack()[2].lineno}, " 285 f"in {inspect.stack()[2].function}\n {(''.join(inspect.stack()[2].code_context or [])).strip(chr(10))}" 286 ) 287 logger.write_debug_to_report(caller) 288 289 @property 290 def baud_rate(self) -> int: 291 """The baud rate for the serial connection.""" 292 return int(self.lock.baud_rate) 293 294 @baud_rate.setter 295 def baud_rate(self, new_baud_rate: int): 296 self.lock.baud_rate = new_baud_rate 297 298 @property 299 def last_status(self) -> StatusCode: 300 """PyVISA status code suppressing exceptions.""" 301 with SuppressExceptions(), self.lock: 302 assert self.lock.resource 303 return self.lock.resource.last_status 304 return StatusCode.success 305 306 @property 307 def timeout(self) -> float: 308 """Get PyVISA timeout value.""" 309 with SuppressExceptions(), self.lock: 310 assert self.lock.resource 311 return self.lock.resource.timeout 312 return 0.0 313 314 @timeout.setter 315 def timeout(self, new_timeout: float): 316 """Set PyVISA timeout value.""" 317 with SuppressExceptions(), self.lock: 318 assert self.lock.resource 319 self.lock.resource.timeout = new_timeout 320 321 def write(self, command: str): 322 """PyVISA write suppressing exceptions.""" 323 with SuppressExceptions(), self.lock: 324 assert self.lock.resource 325 self.lock.resource.write(command) 326 return 327 self.log_caller() 328 329 def query(self, command: str) -> str: 330 """PyVISA query suppressing exceptions.""" 331 with SuppressExceptions(), self.lock: 332 assert self.lock.resource 333 return self.lock.resource.query(command) 334 self.log_caller() 335 return self.default_result 336 337 def read_raw(self) -> bytes: 338 """PyVISA read_raw suppressing exceptions.""" 339 with SuppressExceptions(), self.lock: 340 assert self.lock.resource 341 return self.lock.resource.read_raw() 342 self.log_caller() 343 return b""
Gives multiprocess-safe access to Pyvisa resources without raising exceptions.
281 def log_caller(self): 282 """Log the calling function.""" 283 caller = ( 284 f"File {inspect.stack()[2].filename}, line {inspect.stack()[2].lineno}, " 285 f"in {inspect.stack()[2].function}\n {(''.join(inspect.stack()[2].code_context or [])).strip(chr(10))}" 286 ) 287 logger.write_debug_to_report(caller)
Log the calling function.
289 @property 290 def baud_rate(self) -> int: 291 """The baud rate for the serial connection.""" 292 return int(self.lock.baud_rate)
The baud rate for the serial connection.
298 @property 299 def last_status(self) -> StatusCode: 300 """PyVISA status code suppressing exceptions.""" 301 with SuppressExceptions(), self.lock: 302 assert self.lock.resource 303 return self.lock.resource.last_status 304 return StatusCode.success
PyVISA status code suppressing exceptions.
306 @property 307 def timeout(self) -> float: 308 """Get PyVISA timeout value.""" 309 with SuppressExceptions(), self.lock: 310 assert self.lock.resource 311 return self.lock.resource.timeout 312 return 0.0
Get PyVISA timeout value.
321 def write(self, command: str): 322 """PyVISA write suppressing exceptions.""" 323 with SuppressExceptions(), self.lock: 324 assert self.lock.resource 325 self.lock.resource.write(command) 326 return 327 self.log_caller()
PyVISA write suppressing exceptions.
329 def query(self, command: str) -> str: 330 """PyVISA query suppressing exceptions.""" 331 with SuppressExceptions(), self.lock: 332 assert self.lock.resource 333 return self.lock.resource.query(command) 334 self.log_caller() 335 return self.default_result
PyVISA query suppressing exceptions.
337 def read_raw(self) -> bytes: 338 """PyVISA read_raw suppressing exceptions.""" 339 with SuppressExceptions(), self.lock: 340 assert self.lock.resource 341 return self.lock.resource.read_raw() 342 self.log_caller() 343 return b""
PyVISA read_raw suppressing exceptions.
347@runtime_checkable 348class BMSFlags(Protocol): 349 """The flags passed from hitl_tester to bms_hw.""" 350 351 plateset_id: str = "" 352 dry_run: bool = False 353 doc_generation: bool = False 354 cell_chemistry: str = "" 355 config: dict[str, str | dict[str, Any]] = {} # FIXME(JA): break out into separate attributes with sane defaults 356 report_filename: Path = Path() 357 properties: dict[str, Any] | None = {} 358 test_plan: Path = Path()
The flags passed from hitl_tester to bms_hw.
1767def _no_init_or_replace_init(self, *args, **kwargs): 1768 cls = type(self) 1769 1770 if cls._is_protocol: 1771 raise TypeError('Protocols cannot be instantiated') 1772 1773 # Already using a custom `__init__`. No need to calculate correct 1774 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1775 if cls.__init__ is not _no_init_or_replace_init: 1776 return 1777 1778 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1779 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1780 # searches for a proper new `__init__` in the MRO. The new `__init__` 1781 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1782 # instantiation of the protocol subclass will thus use the new 1783 # `__init__` and no longer call `_no_init_or_replace_init`. 1784 for base in cls.__mro__: 1785 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1786 if init is not _no_init_or_replace_init: 1787 cls.__init__ = init 1788 break 1789 else: 1790 # should not happen 1791 cls.__init__ = object.__init__ 1792 1793 cls.__init__(self, *args, **kwargs)
361class TestFlags(BMSFlags): 362 """The flags to be passed from hitl_tester to bms_hw.""" 363 364 def __init__( 365 self, 366 dry_run: bool = False, 367 doc_generation: bool = False, 368 cell_chemistry: str = "", 369 properties: dict[str, Any] | None = None, 370 test_plan: Path = Path(), 371 ): 372 self.dry_run = dry_run 373 self.doc_generation = doc_generation 374 self.cell_chemistry = cell_chemistry 375 self.properties = properties or {} 376 self.config = {} 377 self.test_plan = test_plan
The flags to be passed from hitl_tester to bms_hw.
364 def __init__( 365 self, 366 dry_run: bool = False, 367 doc_generation: bool = False, 368 cell_chemistry: str = "", 369 properties: dict[str, Any] | None = None, 370 test_plan: Path = Path(), 371 ): 372 self.dry_run = dry_run 373 self.doc_generation = doc_generation 374 self.cell_chemistry = cell_chemistry 375 self.properties = properties or {} 376 self.config = {} 377 self.test_plan = test_plan