hitl_tester.test_cases.bms.test_d300
The test should sample voltage via the DMM at some desired rate and output a CSV with timestamp & voltage.
Used in these test plans:
- m300_dmm ⠀⠀⠀(bms/m300_dmm.plan)
- d300_dmm_2 ⠀⠀⠀(bms/d300_dmm_2.plan)
- d300_dmm_3 ⠀⠀⠀(bms/d300_dmm_3.plan)
Example Command (warning: test plan may run other test cases):
./hitl_tester.py m300_dmm -DSAMPLE_RATE=10 -DDISCHARGE_CUTOFF=1.0
1""" 2The test should sample voltage via the DMM at some desired rate and output a CSV with timestamp & voltage. 3""" 4 5from __future__ import annotations 6 7import datetime 8import time 9from dataclasses import dataclass 10from enum import Enum 11 12import pytest 13from colorama import Fore 14 15from hitl_tester.modules.bms.bms_hw import BMSHardware 16from hitl_tester.modules.bms.m300_dmm import M300Dmm 17from hitl_tester.modules.bms.plateset import Plateset 18from hitl_tester.modules.bms.csv_tables import CSVRecorders, NiCdQC 19from hitl_tester.modules.file_lock import FileEvent 20from hitl_tester.modules.logger import logger 21 22SAMPLE_RATE = 10 23DISCHARGE_CUTOFF = 1.0 24 25bms_hardware = BMSHardware(pytest.flags) # type: ignore[arg-type] 26bms_hardware.init() 27plateset = Plateset() 28 29 30class CellState(Enum): 31 """The current state of the NiCd cell.""" 32 33 UNKNOWN = Fore.MAGENTA 34 PASSED_20S = Fore.LIGHTGREEN_EX 35 PASSED_17S_OR_20S = Fore.GREEN 36 FAILED = Fore.LIGHTRED_EX 37 38 39@dataclass 40class Cell: 41 """A NiCd cell attached to the HITL.""" 42 43 index: int 44 slot: int 45 channel: int 46 timestamp_1210_ma: float | None = None 47 timestamp_1200_ma: float | None = None 48 csv: CSVRecorders | None = None 49 state = CellState.UNKNOWN 50 51 @property 52 def voltage(self): 53 """Measure the voltage for this cell.""" 54 bms_hardware.dmm.scan_index = self.index 55 return bms_hardware.dmm.volts 56 57 def __str__(self) -> str: 58 """Formatted string representation.""" 59 return f"{self.state.value}{self.state.name}{Fore.RESET}" f"[B{self.slot}:C{self.channel}, {self.voltage}V]" 60 61 62def formatted_time(seconds: float | None, start: float | None = 0.0) -> str: 63 """Return time formatted as hh:mm:ss.""" 64 if seconds is not None and start is not None: 65 return str(datetime.timedelta(seconds=seconds - start)).partition(".")[0] 66 return "N/A" 67 68 69def test_record_voltage(): 70 """Record AC current data until killed.""" 71 start_time = time.perf_counter() 72 while True: 73 elapsed_time = time.perf_counter() - start_time 74 volts = bms_hardware.dmm.volts 75 logger.write_info_to_report(f"Elapsed Time(s): {elapsed_time:.3f}, Voltage(V): {volts}") 76 time.sleep(SAMPLE_RATE) 77 78 79def test_record_voltage_nicd(): 80 """Record all voltages.""" 81 assert isinstance(bms_hardware.dmm, M300Dmm) 82 logger.write_info_to_report("Recording...") 83 test_start = time.perf_counter() 84 discharge_start = None 85 kill_event = FileEvent("kill_discharge") 86 discharge_event = FileEvent("nicd_discharging") 87 88 all_cells = [Cell(i, cell["slot"], cell["channel"]) for i, cell in enumerate(bms_hardware.dmm.scan_list)] 89 90 # Initialize cells 91 for cell in all_cells: 92 cell.csv = NiCdQC(bms_hardware) 93 cell.csv.create_file(postfix=f"_{cell.slot:01}{cell.channel:02}") 94 95 # Use a file lock to communicate with main test (locked = test running, released = test ended) 96 run_test = True 97 discharging = False 98 while run_test: 99 time_remaining_seconds = max(0.0, (5 * 3600) - (time.perf_counter() - test_start)) 100 time_remaining_string = formatted_time(time_remaining_seconds) 101 logger.write_info_to_report(f"Time remaining (H:M:S): {time_remaining_string}") 102 logger.write_info_to_report(", ".join(map(str, all_cells))) 103 for cell in all_cells: 104 if (volts := cell.voltage) < DISCHARGE_CUTOFF: 105 run_test = False 106 107 if discharging: 108 if not cell.timestamp_1210_ma and volts <= 1.21: 109 cell.timestamp_1210_ma = time.perf_counter() 110 elif not cell.timestamp_1200_ma and volts <= 1.20: 111 cell.timestamp_1200_ma = time.perf_counter() 112 113 if cell.timestamp_1210_ma - discharge_start >= 5 * 3600: # >5 hours above 1.21V = 20S 114 cell.state = CellState.PASSED_20S 115 elif cell.timestamp_1200_ma - discharge_start >= 5 * 3600: # >5 hours above 1.2V = 17S/20S 116 cell.state = CellState.PASSED_17S_OR_20S 117 else: 118 cell.state = CellState.FAILED 119 elif discharging := discharge_event.is_set(): 120 discharge_start = time.perf_counter() 121 122 # Record CSV 123 cell.csv.record( 124 time.perf_counter() - test_start, 125 time.perf_counter() - discharge_start if discharge_start else None, 126 cell.slot, 127 cell.channel, 128 volts, 129 cell.state.name.title(), 130 int(cell.timestamp_1210_ma - discharge_start) if cell.timestamp_1210_ma else None, 131 int(cell.timestamp_1200_ma - discharge_start) if cell.timestamp_1200_ma else None, 132 ) 133 time.sleep(SAMPLE_RATE) 134 kill_event.set() 135 136 # Log results 137 logger.write_info_to_report("Completed recording") 138 for cell in all_cells: 139 logger.write_info_to_report( 140 f"Board: {cell.slot}, " 141 f"Channel: {cell.channel}, " 142 f"Time to 1.21V (H:M:S): {formatted_time(cell.timestamp_1210_ma, discharge_start)}, " 143 f"Time to 1.20V (H:M:S): {formatted_time(cell.timestamp_1200_ma, discharge_start)}, " 144 f"Result: {cell.state.value}{cell.state.name}{Fore.RESET}" 145 ) 146 time.sleep(5 * 60) # Wait for process to notice event
SAMPLE_RATE =
10
DISCHARGE_CUTOFF =
1.0
bms_hardware =
<hitl_tester.modules.bms.bms_hw.BMSHardware object>
plateset =
<hitl_tester.modules.bms.plateset.Plateset object>
class
CellState(enum.Enum):
31class CellState(Enum): 32 """The current state of the NiCd cell.""" 33 34 UNKNOWN = Fore.MAGENTA 35 PASSED_20S = Fore.LIGHTGREEN_EX 36 PASSED_17S_OR_20S = Fore.GREEN 37 FAILED = Fore.LIGHTRED_EX
The current state of the NiCd cell.
UNKNOWN =
<CellState.UNKNOWN: '\x1b[35m'>
PASSED_20S =
<CellState.PASSED_20S: '\x1b[92m'>
PASSED_17S_OR_20S =
<CellState.PASSED_17S_OR_20S: '\x1b[32m'>
FAILED =
<CellState.FAILED: '\x1b[91m'>
Inherited Members
- enum.Enum
- name
- value
@dataclass
class
Cell:
40@dataclass 41class Cell: 42 """A NiCd cell attached to the HITL.""" 43 44 index: int 45 slot: int 46 channel: int 47 timestamp_1210_ma: float | None = None 48 timestamp_1200_ma: float | None = None 49 csv: CSVRecorders | None = None 50 state = CellState.UNKNOWN 51 52 @property 53 def voltage(self): 54 """Measure the voltage for this cell.""" 55 bms_hardware.dmm.scan_index = self.index 56 return bms_hardware.dmm.volts 57 58 def __str__(self) -> str: 59 """Formatted string representation.""" 60 return f"{self.state.value}{self.state.name}{Fore.RESET}" f"[B{self.slot}:C{self.channel}, {self.voltage}V]"
A NiCd cell attached to the HITL.
Cell( index: int, slot: int, channel: int, timestamp_1210_ma: float | None = None, timestamp_1200_ma: float | None = None, csv: hitl_tester.modules.bms.csv_tables.CSVRecorders | None = None)
state =
<CellState.UNKNOWN: '\x1b[35m'>
def
formatted_time(seconds: float | None, start: float | None = 0.0) -> str:
63def formatted_time(seconds: float | None, start: float | None = 0.0) -> str: 64 """Return time formatted as hh:mm:ss.""" 65 if seconds is not None and start is not None: 66 return str(datetime.timedelta(seconds=seconds - start)).partition(".")[0] 67 return "N/A"
Return time formatted as hh:mm:ss.
def
test_record_voltage():
70def test_record_voltage(): 71 """Record AC current data until killed.""" 72 start_time = time.perf_counter() 73 while True: 74 elapsed_time = time.perf_counter() - start_time 75 volts = bms_hardware.dmm.volts 76 logger.write_info_to_report(f"Elapsed Time(s): {elapsed_time:.3f}, Voltage(V): {volts}") 77 time.sleep(SAMPLE_RATE)
Record AC current data until killed.
def
test_record_voltage_nicd():
80def test_record_voltage_nicd(): 81 """Record all voltages.""" 82 assert isinstance(bms_hardware.dmm, M300Dmm) 83 logger.write_info_to_report("Recording...") 84 test_start = time.perf_counter() 85 discharge_start = None 86 kill_event = FileEvent("kill_discharge") 87 discharge_event = FileEvent("nicd_discharging") 88 89 all_cells = [Cell(i, cell["slot"], cell["channel"]) for i, cell in enumerate(bms_hardware.dmm.scan_list)] 90 91 # Initialize cells 92 for cell in all_cells: 93 cell.csv = NiCdQC(bms_hardware) 94 cell.csv.create_file(postfix=f"_{cell.slot:01}{cell.channel:02}") 95 96 # Use a file lock to communicate with main test (locked = test running, released = test ended) 97 run_test = True 98 discharging = False 99 while run_test: 100 time_remaining_seconds = max(0.0, (5 * 3600) - (time.perf_counter() - test_start)) 101 time_remaining_string = formatted_time(time_remaining_seconds) 102 logger.write_info_to_report(f"Time remaining (H:M:S): {time_remaining_string}") 103 logger.write_info_to_report(", ".join(map(str, all_cells))) 104 for cell in all_cells: 105 if (volts := cell.voltage) < DISCHARGE_CUTOFF: 106 run_test = False 107 108 if discharging: 109 if not cell.timestamp_1210_ma and volts <= 1.21: 110 cell.timestamp_1210_ma = time.perf_counter() 111 elif not cell.timestamp_1200_ma and volts <= 1.20: 112 cell.timestamp_1200_ma = time.perf_counter() 113 114 if cell.timestamp_1210_ma - discharge_start >= 5 * 3600: # >5 hours above 1.21V = 20S 115 cell.state = CellState.PASSED_20S 116 elif cell.timestamp_1200_ma - discharge_start >= 5 * 3600: # >5 hours above 1.2V = 17S/20S 117 cell.state = CellState.PASSED_17S_OR_20S 118 else: 119 cell.state = CellState.FAILED 120 elif discharging := discharge_event.is_set(): 121 discharge_start = time.perf_counter() 122 123 # Record CSV 124 cell.csv.record( 125 time.perf_counter() - test_start, 126 time.perf_counter() - discharge_start if discharge_start else None, 127 cell.slot, 128 cell.channel, 129 volts, 130 cell.state.name.title(), 131 int(cell.timestamp_1210_ma - discharge_start) if cell.timestamp_1210_ma else None, 132 int(cell.timestamp_1200_ma - discharge_start) if cell.timestamp_1200_ma else None, 133 ) 134 time.sleep(SAMPLE_RATE) 135 kill_event.set() 136 137 # Log results 138 logger.write_info_to_report("Completed recording") 139 for cell in all_cells: 140 logger.write_info_to_report( 141 f"Board: {cell.slot}, " 142 f"Channel: {cell.channel}, " 143 f"Time to 1.21V (H:M:S): {formatted_time(cell.timestamp_1210_ma, discharge_start)}, " 144 f"Time to 1.20V (H:M:S): {formatted_time(cell.timestamp_1200_ma, discharge_start)}, " 145 f"Result: {cell.state.value}{cell.state.name}{Fore.RESET}" 146 ) 147 time.sleep(5 * 60) # Wait for process to notice event
Record all voltages.