hitl_tester.test_cases.bms.test_milprf_2590
| Test | MIL-PRF Compatability |
|---|---|
| GitHub Issue(s) | turnaroundfactor/HITL#327 turnaroundfactor/HITL#342 |
| Description | Tests for MIL-PRF-32383/5A (16-December-2021) compatibility. |
Used in these test plans:
- dev_bms ⠀⠀⠀(bms/dev_bms.plan)
- prod_bms ⠀⠀⠀(bms/prod_bms.plan)
- milprf_2590 ⠀⠀⠀(bms/milprf_2590.plan)
- qa_bms ⠀⠀⠀(bms/qa_bms.plan)
Example Command (warning: test plan may run other test cases):
./hitl_tester.py dev_bms -DFAST_MODE=False -DCELL_CAPACITY_AH=0 -DFLASH_SLEEP=7 -DCELL_VOLTAGE=3.8002
1""" 2| Test | MIL-PRF Compatability | 3| :------------------- | :----------------------------------------------------------- | 4| GitHub Issue(s) | turnaroundfactor/HITL#327 </br>\ 5 turnaroundfactor/HITL#342 | 6| Description | Tests for MIL-PRF-32383/5A (16-December-2021) compatibility. | 7""" 8 9from __future__ import annotations 10 11import ctypes 12import math 13import statistics 14import time 15from types import SimpleNamespace 16from typing import cast 17 18import pytest 19 20from hitl_tester.modules.bms.adc_plate import ADCPlate 21from hitl_tester.modules.bms.bms_hw import BMSHardware 22from hitl_tester.modules.bms.bms_serial import serial_monitor 23from hitl_tester.modules.bms.cell import Cell 24from hitl_tester.modules.bms.event_watcher import SerialWatcher 25from hitl_tester.modules.bms.plateset import Plateset 26from hitl_tester.modules.bms.smbus import SMBus 27from hitl_tester.modules.bms.smbus_types import SMBusReg, BatteryMode, BMSCommands 28from hitl_tester.modules.bms.test_handler import CSVRecordEvent 29from hitl_tester.modules.bms_types import DischargeType, ControlStatusRegister, BMSState 30from hitl_tester.modules.logger import logger 31 32FAST_MODE = False 33"""Shorten test times if True.""" 34 35CELL_CAPACITY_AH = 0 36"""Cell capacity combined.""" 37 38FLASH_SLEEP = 7 39"""How long to wait for flash operations in seconds.""" 40 41CELL_VOLTAGE = 3.8002 42"""Default cell voltage.""" 43 44_bms = BMSHardware(pytest.flags) # type: ignore[arg-type] 45_bms.init() 46_plateset = Plateset() 47_adc_plate = ADCPlate() 48_smbus = SMBus() 49_serial_watcher = SerialWatcher() 50 51 52@pytest.fixture(scope="function", autouse=True) 53def reset_test_environment(request): 54 """ 55 Before each test, reset cell sims / BMS and set appropriate temperatures. 56 After each test, clean up modified objects. 57 58 Fixture arguments are provided in an abnormal way, see below tests for details on how to provide these arguments. 59 A default value is used if neither soc nor volts is provided. 60 61 :param float temperature: the initial temperature in C 62 :param float soc: the initial state of charge 63 :param float volts: the initial voltage 64 """ 65 global CELL_CAPACITY_AH 66 67 request.param = getattr(request, "param", {}) 68 69 # Reset cell sims 70 starting_temperature = request.param.get("temperature", 23) 71 if len(_bms.cells) > 0: 72 logger.write_info_to_report(f"Setting temperature to {starting_temperature}°C") 73 _plateset.thermistor1 = _plateset.thermistor2 = starting_temperature 74 75 logger.write_info_to_report("Powering down cell sims") 76 for cell in _bms.cells.values(): 77 cell.disengage_safety_protocols = True 78 cell.volts = 0.0001 79 time.sleep(5) 80 81 for cell in _bms.cells.values(): 82 new_soc = request.param.get("soc") or cell.volts_to_soc(request.param.get("volts")) or 0.50 83 logger.write_info_to_report(f"Powering up cell sim {cell.id} to {new_soc:%}") 84 cell.state_of_charge = new_soc 85 cell.disengage_safety_protocols = False 86 87 logger.write_info_to_report("Waiting 10 seconds for BMS...") 88 time.sleep(10) 89 90 if not CELL_CAPACITY_AH and (serial_data := serial_monitor.read()): # Get capacity from BMS 91 CELL_CAPACITY_AH = float(serial_data["milliamp_hour_capacity"]) / 1000 92 logger.write_info_to_report(f"Setting cell capacity to {CELL_CAPACITY_AH} Ah") 93 for cell in _bms.cells.values(): 94 cell.data.capacity = CELL_CAPACITY_AH 95 96 # Clear permanent disables 97 serial_monitor.read() # Clear latest serial buffer 98 serial_data = serial_monitor.read() 99 for key in serial_data: 100 if key.startswith(("flags.permanent", "flags.measure_output_fets_disabled")) and serial_data[key]: 101 # Erase flash 102 logger.write_warning_to_report("Detected permanent fault.") 103 logger.write_info_to_report("Erasing flash...") 104 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.ERASE_FLASH) 105 time.sleep(FLASH_SLEEP) # Wait for erase to complete 106 data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0] 107 logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}") 108 109 # Enable faults 110 logger.write_info_to_report("Enabling faults...") 111 try: 112 test_fault_enable() 113 except TimeoutError: 114 logger.write_error_to_report("Failed to enable faults.") 115 116 # Recalibrate 117 logger.write_info_to_report("Recalibrating...") 118 try: 119 TestCalibration().test_calibration() 120 except AssertionError: 121 logger.write_error_to_report("Failed to calibrate.") 122 break 123 124 CSVRecordEvent.current_test(request.cls) # Automatically register any tests defined in this class 125 126 yield # Run test 127 128 CSVRecordEvent.failed() # Record results regardless of failure or success 129 CSVRecordEvent.clear_tests() 130 131 132def standard_charge( 133 charge_current: float = 2, 134 max_time: int = 8 * 3600, 135 sample_interval: int = 10, 136 minimum_readings: int = 3, 137 termination_current: float = 0.100, 138): 139 """ 140 Helper function to charge batteries in accordance with 4.3.1 for not greater than three hours. 141 4.3.1 = 23 ± 5°C (73.4°F) ambient pressure/relative humidity, with 2+ hours between charge and discharge. 142 """ 143 _bms.voltage = 16.8 144 _bms.ov_protection = _bms.voltage + 0.050 # 50mV above the charging voltage 145 _bms.current = charge_current 146 _bms.termination_current = termination_current # 100 mA 147 _bms.max_time = max_time 148 _bms.sample_interval = sample_interval 149 _bms.minimum_readings = minimum_readings 150 151 # Run the Charge cycle 152 _plateset.ce_switch = True 153 _bms.run_li_charge_cycle() 154 _plateset.ce_switch = False 155 156 157def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10): 158 """Helper function to stabilize the batteries for 2+ hours.""" 159 _bms.max_time = seconds 160 _bms.sample_interval = sample_interval 161 _bms.run_resting_cycle() 162 163 164def standard_discharge( 165 discharge_current: float = 2, max_time: int = 8 * 3600, sample_interval: int = 10, discharge_voltage: float = 10 166): 167 """Helper function to discharge at 2A until 10V.""" 168 _bms.voltage = discharge_voltage 169 _bms.uv_protection = _bms.voltage - 0.500 # 500mV below voltage cutoff 170 _bms.current = discharge_current 171 _bms.discharge_type = DischargeType.CONSTANT_CURRENT 172 _bms.max_time = max_time 173 _bms.sample_interval = sample_interval 174 175 # Run the discharge cycle, returning the capacity 176 capacity = _bms.run_discharge_cycle() 177 logger.write_info_to_report(f"Discharge complete, capacity was {capacity * 1000.0} mAh") 178 return capacity 179 180 181def test_fault_enable(): 182 """ 183 | Description | Enable faults via SMBus. | 184 | :------------------- | :--------------------------------------------------------------------- | 185 | GitHub Issue | turnaroundfactor/HITL#476 | 186 | Instructions | 1. Enable faults via SMBus </br>\ 187 2. Raise an over-temp fault </br>\ 188 3. Clear the over-temp fault | 189 | Pass / Fail Criteria | Pass if faults can be raised | 190 | Estimated Duration | 1 minute | 191 """ 192 193 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.FAULT_ENABLE) 194 time.sleep(FLASH_SLEEP) 195 196 # Check if overtemp faults can be raised. 197 timeout_s = 30 198 199 # Raise a fault 200 _plateset.thermistor1 = 65 201 start = time.perf_counter() 202 while (serial_data := serial_monitor.read(latest=True)) and not serial_data["flags.fault_overtemp_discharge"]: 203 if time.perf_counter() - start > timeout_s: 204 message = f"Over-temperature fault was not raised after {timeout_s} seconds." 205 logger.write_failure_to_html_report(message) 206 raise TimeoutError(message) 207 logger.write_result_to_html_report("Fault successfully raised.") 208 209 # Clear the fault 210 _plateset.thermistor1 = 45 211 start = time.perf_counter() 212 while (serial_data := serial_monitor.read(latest=True)) and serial_data["flags.fault_overtemp_discharge"]: 213 if time.perf_counter() - start > timeout_s: 214 message = f"Over-temperature fault was not cleared after {timeout_s} seconds." 215 logger.write_failure_to_html_report(message) 216 raise TimeoutError(message) 217 logger.write_result_to_html_report("Fault successfully cleared.") 218 219 220class TestCalibration: 221 """Calibrate the BMS if needed.""" 222 223 average = 0 224 readings = 0 225 226 def bms_current(self): 227 """Measure serial current and calculate an average.""" 228 assert (serial_date := serial_monitor.read()), "Could not read serial." 229 new_reading = serial_date["mamps"] 230 self.average = (new_reading + self.readings * self.average) / (self.readings + 1) 231 self.readings += 1 232 logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}") # Output current on every sample 233 234 def test_calibration(self): 235 """ 236 | Description | Test calibration retention | 237 | :------------------- | :--------------------------------------------------------------------- | 238 | GitHub Issue | turnaroundfactor/HITL#413 | 239 | Instructions | 1. Calibrate the BMS </br>\ 240 2. Confirm BMS is calibrated | 241 | Pass / Fail Criteria | Pass if calibrated | 242 | Estimated Duration | 1 minute | 243 """ 244 acceptable_error_ma = 5 245 scan_count = 6 # How many measurements to take for an average. 246 247 self.readings = 0 # Reset average 248 for _ in range(scan_count): # Measure current over some period 249 self.bms_current() 250 time.sleep(5) 251 logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA") 252 offset = int(round(self.average, 0)) 253 logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA") 254 data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE 255 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data) 256 time.sleep(FLASH_SLEEP) 257 data = cast(int, _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]) 258 logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}") 259 260 # Confirm current is in acceptable range 261 self.readings = 0 # Reset average 262 for _ in range(scan_count): 263 self.bms_current() 264 time.sleep(5) 265 logger.write_result_to_html_report(f"Average rest current (calibrated): {self.average:.3f} mA") 266 assert ( 267 acceptable_error_ma > self.average > -acceptable_error_ma 268 ), f"{self.average:.3f} mA outside limit of ±{acceptable_error_ma:.3f} mA" 269 270 271def test_charge_enable_on(): 272 """ 273 | Description | Confirm charging above 400mA works when CE is active | 274 | :------------------- | :--------------------------------------------------------------- | 275 | GitHub Issue | turnaroundfactor/HITL#342 | 276 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 277 | MIL-PRF Requirements | The charge enable terminal shall comply with the following: </br>\ 278 ⠀⠀a. Maximum charge without enable: 400 mA </br>\ 279 ⠀⠀b. Equivalent resistor: 235 Ω </br>\ 280 ⠀⠀c. Equivalent diode VF: 1.3 V </br>\ 281 ⠀⠀d. Approximate activation current: 7 mA | 282 | Instructions | 1. Activate CE </br>\ 283 2. Charge at 2A | 284 | Pass / Fail Criteria | Pass if current is more than 400mA | 285 | Estimated Duration | 1 minute | 286 | Note | This test can fail if the battery is sufficiently charged. | 287 """ 288 passing_current = 0.400 289 290 # Enable charging and timer 291 _plateset.ce_switch = True 292 _bms.charger.set_profile(volts=16.8, amps=2) 293 _bms.charger.enable() 294 _bms.timer.reset() # Keep track of runtime 295 296 # Charge until current is more than passing_current or timeout 297 timeout_seconds = 10 298 while (latest_current := _bms.charger.amps) < passing_current and _bms.timer.elapsed_time <= timeout_seconds: 299 logger.write_info_to_report( 300 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 301 f"Current: {latest_current:.3f}" 302 ) 303 time.sleep(1) 304 305 # Charging is complete, turn off the charger 306 _bms.charger.disable() 307 _plateset.ce_switch = False 308 309 # Check results 310 logger.write_result_to_html_report(f"Current: {latest_current:.3f} A ≥ {passing_current:.3f} A") 311 if latest_current < passing_current: 312 pytest.fail(f"Current of {latest_current:.3f} A does not exceed {passing_current:.3f} A.") 313 314 315def test_charge_enable_off(): 316 """ 317 | Description | Confirm charging above 400mA doesn't work when CE is inactive | 318 | :------------------- | :--------------------------------------------------------------------- | 319 | GitHub Issue | turnaroundfactor/HITL#342 | 320 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 321 | MIL-PRF Requirements | The charge enable terminal shall comply with the following: </br>\ 322 ⠀⠀a. Maximum charge without enable: 400 mA </br>\ 323 ⠀⠀b. Equivalent resistor: 235 Ω </br>\ 324 ⠀⠀c. Equivalent diode VF: 1.3 V </br>\ 325 ⠀⠀d. Approximate activation current: 7 mA | 326 | Instructions | 1. Increment charge current every second </br>\ 327 2. Stop when charge current drops to ~0A | 328 | Pass / Fail Criteria | Pass if the highest current is less than 400mA | 329 | Estimated Duration | 5 minutes | 330 """ 331 failing_current = 0.400 332 uncertainty = 0.005 333 334 # Enable charging and timer 335 _plateset.ce_switch = False 336 _bms.charger.set_profile(volts=16.8, amps=0.010) # Starting current 337 _bms.charger.enable() 338 _bms.timer.reset() # Keep track of runtime 339 340 # Charge until current drops below some threshold (can float above 0) or is more than failing_current 341 max_charge_current = 0 342 while ( 343 _bms.charger.target_amps <= failing_current + uncertainty 344 and abs(_bms.charger.target_amps - (latest_current := _bms.charger.amps)) <= uncertainty 345 ): 346 # while (failing_current + uncertainty * 2) > (latest_current := _bms.charger.amps) > 0.005: 347 max_charge_current = max(max_charge_current, latest_current) 348 logger.write_info_to_report( 349 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 350 f"Current: {latest_current:.3f}" 351 ) 352 # Increase by 1 mA / second 353 _bms.charger.set_profile(volts=_bms.charger.target_volts, amps=_bms.charger.target_amps + 0.001) 354 time.sleep(1) 355 356 # Charging is complete, turn off the charger 357 _bms.charger.disable() 358 359 # Check results 360 logger.write_result_to_html_report(f"Current: {max_charge_current:.3f} A ≤ {failing_current:.3f} ± {uncertainty} A") 361 if max_charge_current > failing_current + uncertainty: 362 pytest.fail(f"Current of {max_charge_current:.3f} A exceeds {failing_current:.3f} A limit.") 363 364 365def test_taf_charge_enable_off(): 366 """ 367 | Description | TAF: Confirm charging above 20mA doesn't work when CE is inactive | 368 | :------------------- | :--------------------------------------------------------------------- | 369 | GitHub Issue | turnaroundfactor/HITL#342 | 370 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 371 | Instructions | 1. Increment charge current every second </br>\ 372 2. Stop when charge current drops to ~0A | 373 | Pass / Fail Criteria | Pass if the highest current is less than or equal to 20mA | 374 | Estimated Duration | 1 minute | 375 | Note | We want to disable if current is over 20 mA since \ 376 that's what BT does, and it's a safer way to charge. Other OTS \ 377 may have different cutoffs, and the BT one was measured by experiment. | 378 """ 379 target_current = 0.020 380 uncertainty = 0.005 381 382 # Enable charging and timer 383 _plateset.ce_switch = False 384 _bms.charger.set_profile(volts=16.8, amps=0.010) # Starting current 385 _bms.charger.enable() 386 _bms.timer.reset() # Keep track of runtime 387 388 # Charge until current drops below some threshold (can float above 0) or is more than failing_current 389 max_charge_current = 0 390 while ( 391 _bms.charger.target_amps <= target_current + uncertainty 392 and abs(_bms.charger.target_amps - (latest_current := _bms.charger.amps)) <= uncertainty 393 ): 394 max_charge_current = max(max_charge_current, latest_current) 395 logger.write_info_to_report( 396 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 397 f"Current: {latest_current:.3f}" 398 ) 399 # Increase by 1 mA / second 400 _bms.charger.set_profile(volts=_bms.charger.target_volts, amps=_bms.charger.target_amps + 0.001) 401 time.sleep(1) 402 403 # Charging is complete, turn off the charger 404 _bms.charger.disable() 405 406 # Check results 407 logger.write_result_to_html_report(f"Current: {max_charge_current:.3f} A = {target_current:.3f} ± {uncertainty} A") 408 if not target_current - uncertainty <= max_charge_current <= target_current + uncertainty: 409 pytest.fail(f"Current of {max_charge_current:.3f} A exceeds {target_current:.3f} A limit.") 410 411 412@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 413class TestExtendedCycle: 414 """Perform a long charge / discharge.""" 415 416 class CellVoltageDiscrepancy(CSVRecordEvent): 417 """@private Compare cell sim voltage to reported cell voltage.""" 418 419 allowable_error = 0.01 420 max = SimpleNamespace(cell_id=0, sim_v=0, bms_v=0, error=0.0) 421 422 @classmethod 423 def failed(cls) -> bool: 424 """Check if test parameters were exceeded.""" 425 return bool(cls.max.error > cls.allowable_error) 426 427 @classmethod 428 def verify(cls, row, serial_data, _cell_data): 429 """Cell voltage within range""" 430 for i, cell_id in enumerate(_bms.cells): 431 row_data = SimpleNamespace( 432 cell_id=cell_id, 433 sim_v=row[f"ADC Plate Cell {cell_id} Voltage (V)"], 434 bms_v=serial_data[f"mvolt_cell{'' if i == 0 else i}"] / 1000, 435 ) 436 row_data.error = abs((row_data.bms_v - row_data.sim_v) / row_data.sim_v) 437 cls.max = max(cls.max, row_data, key=lambda data: data.error) 438 439 @classmethod 440 def result(cls): 441 """Detailed test result information.""" 442 return ( 443 f"Cell Voltage error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 444 f"(Sim {cls.max.cell_id}: {cls.max.sim_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 445 ) 446 447 class CurrentDiscrepancy(CSVRecordEvent): 448 """@private Compare terminal current to reported current.""" 449 450 allowable_error = 0.015 451 max = SimpleNamespace(hitl_a=0, bms_a=0, error=0.0) 452 453 @classmethod 454 def failed(cls) -> bool: 455 """Check if test parameters were exceeded.""" 456 return bool(cls.max.error > cls.allowable_error) 457 458 @classmethod 459 def verify(cls, row, serial_data, _cell_data): 460 """Current within range""" 461 row_data = SimpleNamespace(hitl_a=row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 462 row_data.error = abs((row_data.bms_a - row_data.hitl_a) / row_data.hitl_a) 463 if abs(row_data.hitl_a) > 0.100: # Ignore currents within 100mA to -100mA 464 cls.max = max(cls.max, row_data, key=lambda data: data.error) 465 466 @classmethod 467 def result(cls): 468 """Detailed test result information.""" 469 return ( 470 f"Current error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 471 f"(HITL: {cls.max.hitl_a * 1000:.1f} mA, BMS: {cls.max.bms_a * 1000:.1f} mA)" 472 ) 473 474 class TerminalVoltageDiscrepancy(CSVRecordEvent): 475 """@private Compare HITL voltage to reported Terminal voltage.""" 476 477 allowable_error = 0.015 478 max = SimpleNamespace(hitl_v=0, bms_v=0, error=0.0) 479 480 @classmethod 481 def failed(cls) -> bool: 482 """Check if test parameters were exceeded.""" 483 return bool(cls.max.error > cls.allowable_error) 484 485 @classmethod 486 def verify(cls, row, serial_data, _cell_data): 487 """Terminal voltage within range""" 488 row_data = SimpleNamespace(hitl_v=row["HITL Voltage (V)"], bms_v=serial_data["mvolt_terminal"] / 1000) 489 row_data.error = abs((row_data.bms_v - row_data.hitl_v) / row_data.hitl_v) 490 cls.max = max(cls.max, row_data, key=lambda data: data.error) 491 492 @classmethod 493 def result(cls): 494 """Detailed test result information.""" 495 return ( 496 f"Terminal Voltage error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 497 f"(HITL: {cls.max.hitl_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 498 ) 499 500 class TemperatureDiscrepancyTherm1(CSVRecordEvent): 501 """@private Compare HITL temperature to reported temperature.""" 502 503 allowable_error = 5.0 504 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 505 506 @classmethod 507 def failed(cls) -> bool: 508 """Check if test parameters were exceeded.""" 509 return bool(cls.max.error > cls.allowable_error) 510 511 @classmethod 512 def verify(cls, _row, serial_data, _cell_data): 513 """Temperature within range""" 514 row_data = SimpleNamespace(hitl_c=_plateset.thermistor1, bms_c=serial_data["dk_temp"] / 10 - 273) 515 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 516 cls.max = max(cls.max, row_data, key=lambda data: data.error) 517 518 @classmethod 519 def result(cls): 520 """Detailed test result information.""" 521 return ( 522 f"Thermistor 1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 523 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 524 ) 525 526 class TemperatureDiscrepancyTherm2(CSVRecordEvent): 527 """@private Compare HITL temperature to reported temperature.""" 528 529 allowable_error = 5.0 530 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 531 532 @classmethod 533 def failed(cls) -> bool: 534 """Check if test parameters were exceeded.""" 535 return bool(cls.max.error > cls.allowable_error) 536 537 @classmethod 538 def verify(cls, _row, serial_data, _cell_data): 539 """Temperature within range""" 540 row_data = SimpleNamespace(hitl_c=_plateset.thermistor2, bms_c=serial_data["dk_temp1"] / 10 - 273) 541 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 542 cls.max = max(cls.max, row_data, key=lambda data: data.error) 543 544 @classmethod 545 def result(cls): 546 """Detailed test result information.""" 547 return ( 548 f"Thermistor 2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 549 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 550 ) 551 552 class NoFaults(CSVRecordEvent): 553 """@private Check for any faults.""" 554 555 faults: list[str] = [] 556 557 @classmethod 558 def failed(cls) -> bool: 559 """Check if test parameters were exceeded.""" 560 return len(cls.faults) != 0 561 562 @classmethod 563 def verify(cls, _row, serial_data, _cell_data): 564 """Check for faults.""" 565 for key in serial_data: 566 if "fault" in key and key.startswith("flags.") and serial_data[key]: 567 cls.faults.append(key.removeprefix("flags.").title()) 568 569 @classmethod 570 def result(cls): 571 """Detailed test result information.""" 572 return f"Faults encountered: {' | '.join(cls.faults) or None}" 573 574 class NoReset(CSVRecordEvent): 575 """@private Check for resets.""" 576 577 reset_reasons = ControlStatusRegister(0) 578 579 @classmethod 580 def failed(cls) -> bool: 581 """Check if test parameters were exceeded.""" 582 return bool(cls.reset_reasons) 583 584 @classmethod 585 def verify(cls, _row, serial_data, _cell_data): 586 """Check if any resets occurred.""" 587 reset_reason = ControlStatusRegister(serial_data.get("Reset_Flags", 0)) 588 reset_reason &= ~ControlStatusRegister.POWER & ~ControlStatusRegister.RESET_PIN # Ignore valid reasons 589 cls.reset_reasons |= reset_reason 590 591 @classmethod 592 def result(cls): 593 """Detailed test result information.""" 594 return f"Resets encountered: {str(cls.reset_reasons) or None}" 595 596 class SOCDiscrepancy(CSVRecordEvent): 597 """@private Compare lowest HITL cell SOC to reported SOC.""" 598 599 allowable_error = 0.05 600 max = SimpleNamespace(sim_id=0, sim_soc=0, bms_soc=0, error=0.0) 601 602 @classmethod 603 def failed(cls) -> bool: 604 """Check if test parameters were exceeded.""" 605 return bool(cls.max.error > cls.allowable_error) 606 607 @classmethod 608 def verify(cls, _row, serial_data, cell_data): 609 """SOC within range.""" 610 lowest_sim_soc_id = min(cell_data, key=lambda cell_id: cell_data[cell_id]["state_of_charge"]) 611 lowest_sim_soc = cell_data[lowest_sim_soc_id]["state_of_charge"] 612 row_data = SimpleNamespace( 613 sim_id=lowest_sim_soc_id, sim_soc=lowest_sim_soc, bms_soc=serial_data["percent_charged"] / 100 614 ) 615 row_data.error = abs(row_data.bms_soc - row_data.sim_soc) 616 cls.max = max(cls.max, row_data, key=lambda data: data.error) 617 618 @classmethod 619 def result(cls): 620 """Detailed test result information.""" 621 return ( 622 f"SOC error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 623 f"(Sim {cls.max.sim_id}: {cls.max.sim_soc:.1%}, BMS: {cls.max.bms_soc:.1%})" 624 ) 625 626 class HealthChangeCount(CSVRecordEvent): 627 """@private Check how many times Health changes.""" 628 629 changes = 0 630 allowable_changes = 1 631 last_reported_health: int | None = None 632 633 @classmethod 634 def failed(cls) -> bool: 635 """Check if test parameters were exceeded.""" 636 return bool(cls.changes > cls.allowable_changes) 637 638 @classmethod 639 def verify(cls, _row, serial_data, _cell_data): 640 """Detect health change.""" 641 if serial_data["percent_health"] != cls.last_reported_health: 642 cls.changes += cls.last_reported_health is not None 643 cls.last_reported_health = serial_data["percent_health"] 644 645 @classmethod 646 def result(cls): 647 """Detailed test result information.""" 648 return f"Health change#: {cls.cmp(cls.changes, '<=', cls.allowable_changes, form='d')}" 649 650 class HealthChange(CSVRecordEvent): 651 """@private Check health change.""" 652 653 allowable_change = 0.01 654 max_change: float = 0.0 655 initial_health: float | None = None 656 657 @classmethod 658 def failed(cls) -> bool: 659 """Check if test parameters were exceeded.""" 660 return bool(cls.max_change > cls.allowable_change) 661 662 @classmethod 663 def verify(cls, _row, serial_data, _cell_data): 664 """Health change within range.""" 665 if cls.initial_health is None: 666 cls.initial_health = serial_data["percent_health"] / 100 667 cls.max_change = max(cls.max_change, abs(cls.initial_health - serial_data["percent_health"] / 100)) 668 669 @classmethod 670 def result(cls): 671 """Detailed test result information.""" 672 return f"Health change: {cls.cmp(cls.max_change, '<=', cls.allowable_change)}" 673 674 class UsedAhDiscrepancy(CSVRecordEvent): 675 """@private Compare HITL used Ah to reported used Ah.""" 676 677 allowable_error = 0.01 678 max = SimpleNamespace(hitl_ah=0, bms_ah=0, error=0.0) 679 initial_charge_cycle: int | None = None 680 681 @classmethod 682 def failed(cls) -> bool: 683 """Check if test parameters were exceeded.""" 684 return bool(cls.max.error > cls.allowable_error) 685 686 @classmethod 687 def verify(cls, row, serial_data, _cell_data): 688 """Check used Ah during discharge.""" 689 if cls.initial_charge_cycle is None: 690 cls.initial_charge_cycle = serial_data["charge_cycles"] 691 delta_cycle = serial_data["charge_cycles"] - cls.initial_charge_cycle 692 row_data = SimpleNamespace( 693 hitl_ah=-(row["HITL Capacity (Ah)"] or 0), 694 bms_ah=(serial_data["milliamp_hour_used"] + delta_cycle * serial_data["milliamp_hour_capacity"]) / 1000, 695 ) 696 if row["Cycle"] == "run_discharge_cycle" and row_data.hitl_ah > 0.100: 697 logger.write_debug_to_report(f" In discharge: {row_data.hitl_ah} Ah") 698 row_data.error = abs((row_data.bms_ah - row_data.hitl_ah) / row_data.hitl_ah) 699 cls.max = max(cls.max, row_data, key=lambda data: data.error) 700 701 @classmethod 702 def result(cls): 703 """Detailed test result information.""" 704 return ( 705 f"Used Ah error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 706 f"(HITL: {cls.max.hitl_ah * 1000:.1f} mAh, BMS: {cls.max.bms_ah * 1000:.1f} mAh)" 707 ) 708 709 class ChargeCycle(CSVRecordEvent): 710 """@private Compare cell sim voltage to reported cell voltage.""" 711 712 allowable_cycles = 1 713 initial_cycle: int | None = None 714 max_cycle = 0 715 716 @classmethod 717 def failed(cls) -> bool: 718 """Check if test parameters were exceeded.""" 719 return cls.initial_cycle is None or bool(cls.max_cycle - cls.initial_cycle > cls.allowable_cycles) 720 721 @classmethod 722 def verify(cls, _row, serial_data, _cell_data): 723 """Cell voltage within range""" 724 if cls.initial_cycle is None: 725 cls.initial_cycle = serial_data["charge_cycles"] 726 cls.max_cycle = max(cls.max_cycle, serial_data["charge_cycles"]) 727 728 @classmethod 729 def result(cls): 730 """Detailed test result information.""" 731 return ( 732 f"Cycle change: " 733 f"{cls.cmp(cls.max_cycle - (cls.initial_cycle or 0), '<=', cls.allowable_cycles, form='d')} " 734 f"(Starting: {cls.initial_cycle}, Ending: {cls.max_cycle})" 735 ) 736 737 def test_extended_cycle(self): 738 """ 739 | Description | Perform a long charge / discharge | 740 | :------------------- | :--------------------------------------------------------------------- | 741 | GitHub Issue | turnaroundfactor/HITL#342 | 742 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 743jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 744 | MIL-PRF Sections | 4.7.2.3 (Capacity discharge) </br>\ 745 4.6.1 (Standard Charge) </br>\ 746 4.3.1 (Normal conditions) </br>\ 747 3.5.3 (Capacity) | 748 | Instructions | 1. Set thermistors to 23C </br>\ 749 2. Put cells in a rested state at 2.5V per cell </br>\ 750 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 751 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours) </br>\ 752 5. Discharge at 2A until battery reaches 10V then stop discharging | 753 | Pass / Fail Criteria | ⦁ Serial cell voltage agrees with HITL cell voltage to within 1% </br>\ 754 ⦁ Serial terminal current agrees with HITL current to within 1% </br>\ 755 ⦁ Serial terminal voltage agrees with HITL voltage to within 1% </br>\ 756 ⦁ Serial thermistor 1 and 2 agree with HITL thermistor 1 and 2 to within 5°C </br>\ 757 ⦁ No Fault Flags over entire duration of test </br>\ 758 ⦁ No resets occur over entire duration of test </br>\ 759 ⦁ Serial cell SOC agrees with lowest HITL cell SOC to within 5% SOC </br>\ 760 ⦁ Serial Health shall only change once </br>\ 761 ⦁ Serial Health shall not change by more than 1% </br>\ 762 ⦁ Serial used Ah agrees with HITL Ah to within 1% (for abs(HITL Ah > 100mAh)) </br>\ 763 ⦁ Serial charge cycle shall only increment once </br>\ 764 ⦁ E-ink display is operational [TBD How to do this] | 765 | Estimated Duration | 12 hours | 766 | Note | MIL-PRF 4.7.2.3.1 (Initial capacity discharge): Each battery subjected \ 767 to the capacity discharge test above (see 4.7.2.3) on its initial \ 768 charge/discharge cycle is permitted up to three cycles to meet the \ 769 capacity discharge test requirement (see 3.1). Any battery not meeting \ 770 the specified capacity discharge requirement (see 3.1) during any of \ 771 its first three cycles is considered a failure | 772 """ 773 # FIXME(JA): adjust estimated duration based on first test 774 775 standard_charge() 776 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 777 standard_discharge() 778 779 # Check results 780 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 781 pytest.fail(CSVRecordEvent.result()) 782 783 784@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7}], indirect=True) 785class TestSMBusWritableRegisters: 786 """SMBus Writable Registers""" 787 788 def test_smbus_writable_registers(self) -> None: 789 """ 790 | Description | Validate SMBus Writable Registers | 791 | :------------------- | :--------------------------------------------------------------------- | 792 | GitHub Issue | turnaroundfactor/HITL#397 | 793 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 794jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 795 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 796 | Instructions | 1. Set thermistors to 23C </br>\ 797 2. Put cells in a rested state at 3.7V per cell </br>\ 798 3. Host to Battery Read Word (Register 1): 0x00 </br>\ 799 5. Host to Battery Read Word (Register 2): 0x0438 </br>\ 800 6. Host to Battery Write Word (Register 2): 0xBEEF </br>\ 801 7. Host to Battery Read Word (Register 2): 0xBEEF </br>\ 802 8. Host to Battery Read Word (Register 3): 0x000A </br>\ 803 9. Host to Battery Write Word (Register 3): 0xBEEF </br>\ 804 10. Host to Battery Read Word (Register 3): 0xBEEF </br>\ 805 11. Host to Battery Write Word (Register 3): 0x000A </br>\ 806 12. Host to Battery Read Word (Register 3): 0x000A </br>\ 807 13. Host to Battery Read Word (Register 4): 000xx00x0010 (where x is "don't care") | 808 | Pass / Fail Criteria | ⦁ ManufacturerAccess (Battery Register 1) returns 0x00 </br>\ 809 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0x0438 </br>\ 810 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0xBEEF </br>\ 811 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 812 ⦁ Remaining Time Alarm (Battery Register 3) returns 0xBEEF </br>\ 813 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 814 ⦁ Battery Mode (Battery Register 4) returns 000xx00x0010 | 815 | Estimated Duration | 30 seconds | 816 | Note | When specified (see 3.1), batteries shall be compliant with System \ 817 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 818 Data (SBData) Specification, version 1.1, with the exception that \ 819 SBData safety signal hardware requirements therein shall be replaced \ 820 with a charge enable when a charge enable is specified (see 3.1 and \ 821 3.5.6). Certification is required. Batteries shall be compatible \ 822 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 823 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 824 accurate within +0/-5% of the actual state of charge for the battery \ 825 under test throughout the discharge. Manufacturer and battery data \ 826 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 827 logic circuitry. Pull-up resistors will be provided by the charger. \ 828 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 829 Smart batteries may act as master or slave on the bus, but must \ 830 perform bus master timing arbitration according to the SMBus \ 831 specification when acting as master. | 832 """ 833 time.sleep(30) 834 835 assert (serial_data := serial_monitor.read()) 836 ten_percent_capacity = float(serial_data["milliamp_hour_capacity"]) // 10 837 default_value = 0x000A 838 easy_spot_value = 0xBEEF 839 smbus_registers = [ 840 {"sm_register": SMBusReg.MANUFACTURING_ACCESS, "data": [0]}, 841 { 842 "sm_register": SMBusReg.REMAINING_CAPACITY_ALARM, 843 "data": [ten_percent_capacity, easy_spot_value], 844 }, 845 { 846 "sm_register": SMBusReg.REMAINING_TIME_ALARM, 847 "data": [default_value, easy_spot_value, default_value], 848 }, 849 ] 850 failed_tests = [] 851 852 # Manufacturing Access, Remaining Capacity Alarm, Remaining Time Alarm 853 for smbus_register in smbus_registers: 854 register = cast(SMBusReg, smbus_register["sm_register"]) 855 index = 0 856 for elem in map(int, cast(list[int], smbus_register["data"])): 857 if index > 0: 858 logger.write_info_to_report(f"Writing {hex(elem)} to {register.fname}") 859 _smbus.write_register(register, elem) 860 861 read_sm_response = _smbus.read_register(register) 862 863 expected_value = elem.to_bytes(2, byteorder="little") 864 if read_sm_response[1] != expected_value: 865 logger.write_result_to_html_report( 866 f"{register.fname} did not have expected result of {hex(elem)}, " 867 f"instead was: {read_sm_response[1]!r}" 868 ) 869 logger.write_warning_to_report( 870 f"{register.fname} did not have expected result of {hex(elem)}, " 871 f"instead was: {read_sm_response[1]!r}" 872 ) 873 failed_tests.append(register.fname) 874 elif index == 0: 875 message = f"{register.fname} passed reading default value {hex(elem)}" 876 logger.write_result_to_html_report(message) 877 logger.write_info_to_report(message) 878 else: 879 message = f"{register.fname} had value correctly changed to {hex(elem)}" 880 logger.write_result_to_html_report(message) 881 logger.write_info_to_report(message) 882 883 index += 1 884 885 # BatteryMode 886 # TODO: BatteryMode will be updated at later date 887 888 # mask = 0b111001101111 889 # pattern = 0b000000000010 # 000xx00x0010 890 battery_mode = SMBusReg.BATTERY_MODE 891 read_bm_response = _smbus.read_register(battery_mode) 892 # bm_bytes = int.from_bytes(read_bm_response[1], byteorder="little") 893 # logger.write_info_to_report(f"Bytes of {battery_mode.fname}: {bm_bytes}") 894 895 # masked_response = mask & bm_bytes 896 expected_value = 0x0000.to_bytes(2, byteorder="little") 897 if read_bm_response[1] == expected_value: 898 logger.write_info_to_report(f"{battery_mode.fname} passed response check") 899 else: 900 logger.write_warning_to_report(f"{battery_mode.fname} did not have expected result of: 0x0000") 901 failed_tests.append(battery_mode.fname) 902 903 # Overall results report 904 if failed_tests: 905 failed_registers = list(dict.fromkeys(failed_tests)) 906 message = f"{len(failed_registers)} register(s) failed at least one test: {', '.join(failed_registers)}" 907 logger.write_result_to_html_report(f"<font color='#990000'> {message}</font>") 908 pytest.fail(message) 909 else: 910 logger.write_result_to_html_report("All tested registers passed writable test") 911 912 913def analyze_register_response( 914 requirements: list[dict[str, SMBusReg | float] | dict[str, SMBusReg | int] | dict[str, SMBusReg | bool]], 915 failed_list: list[str], 916 at_rate: int, 917) -> list[str]: 918 """Reads SMBus register response and validates""" 919 920 full_amount = 0 921 remaining_amount = 0 922 923 for elem in requirements: 924 register: SMBusReg = cast(SMBusReg, elem["register"]) 925 requirement = elem["requirement"] 926 927 read_response = _smbus.read_register(register) 928 if requirement is True: 929 if not read_response[0]: 930 message = f"Invalid response for {register.fname}. Expected non-zero value, but got {read_response[0]}" 931 logger.write_warning_to_report(message) 932 failed_list.append(f"{register.fname} after setting AtRate value to {at_rate}(mA)") 933 message = f"Received valid response for {register.fname}." 934 logger.write_info_to_report(message) 935 936 else: 937 assert isinstance(read_response[0], int | float) and isinstance(requirement, int | float) 938 if not math.isclose(read_response[0], requirement, rel_tol=0.1): 939 message = f"Invalid response for {register.fname}. Expected {requirement}, but got {read_response[0]}" 940 logger.write_warning_to_report(message) 941 failed_list.append(f"{register.fname} after setting AtRate value to {at_rate}(mA)") 942 943 message = f"Received valid response for {register.fname}" 944 logger.write_info_to_report(message) 945 946 if register == SMBusReg.AT_RATE_TIME_TO_FULL: 947 assert isinstance(read_response[0], int) 948 full_amount = read_response[0] 949 950 if register == SMBusReg.AT_RATE_TIME_TO_EMPTY: 951 assert isinstance(read_response[0], int) 952 remaining_amount = read_response[0] 953 954 logger.write_result_to_html_report(f"{at_rate} AtRate, {remaining_amount} Remaining, {full_amount} Full") 955 return failed_list 956 957 958# @pytest.mark.parametrize("reset_test_environment", [{"volts": 3.5, "temperature": 15, "soc": 0.80}], indirect=True) 959class TestSMBusAtRate: 960 """Validate the SMBus AtRate Commands""" 961 962 def test_smbus_at_rate(self): 963 # TODO: Update Documentation 964 """ 965 | Description | Validate SMBus AtRate Commands | 966 | :------------------- | :--------------------------------------------------------------------- | 967 | GitHub Issue | turnaroundfactor/HITL#398 | 968 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 969jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 970 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 971 | Instructions | 1. Write word 1000 (unit is mA) to AtRate[Register 0x4] </br>\ 972 2. Read word from AtRateTimeToFull[0x5] </br>\ 973 3. Read word from AtRate TimeToEmpty[0x6] </br>\ 974 4. Read word from AtRateOK[0x7] </br>\ 975 5. Write word -1000 to AtRate[Register 0x4] </br>\ 976 6. Read word from AtRateTimeToFull[0x5] </br>\ 977 7. Read word from AtRateTimeToEmpty[0x6] </br>\ 978 8. Charge battery at 2A </br>\ 979 9. Read word from AtRateOK[0x7] </br>\ 980 10. Charge battery at 0.1A </br>\ 981 11. Read word from AtRateOK[0x7] </br>\ 982 12. Discharge battery at 2A </br>\ 983 13. Read word from AtRateOK[0x7] </br>\ 984 14. Discharge battery at 0.1A </br>\ 985 15. Read word from AtRateOK[0x7] </br>\ 986 16. Write word 0 to AtRate[Register 0x4] </br>\ 987 17. Read word from AtRateTimeToFull[0x6] </br>\ 988 18. Read word from AtRateTimeToEmpty[0x6] </br>\ 989 19. Read word from AtRateOK[0x7] </br>\ 990 20. Write word 0x8000 to BatteryMode[Register 0x3] </br>\ 991 21. Write word 0x0808 to BatteryMode[Register 0x3] | 992 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 993 ⦁ Expect AtRateTimeToFull to be FullChargeCapacity-RemainingCapacity/1000/60 </br>\ 994 ⦁ Expect AtRateTimeToEmpty to be 65,635 </br>\ 995 ⦁ Expect AtRateOK to be True (non-zero) </br>\ 996 Discharging(ma) ---- </br>\ 997 ⦁ Expect AtRateTimeToFull to be 65,535 </br>\ 998 ⦁ Expect AtRateTimeToEmpty to be RemainingCapacity / 1000 / 60 </br>\ 999 ⦁ Expect AtRateOK to be True (non-zero) for all charge changes </br>\ 1000 Rest (mA) ---- </br>\ 1001 ⦁ Expect AtRAteTimeToFull to be 65,535 </br>\ 1002 ⦁ Expect AtRateTimeToEmpty to be 65,535 </br>\ 1003 ⦁ Expect AtRateOK to be True (non-zero) | 1004 | Estimated Duration | 10 seconds | 1005 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1006 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1007 Data (SBData) Specification, version 1.1, with the exception that \ 1008 SBData safety signal hardware requirements therein shall be replaced \ 1009 with a charge enable when a charge enable is specified (see 3.1 and \ 1010 3.5.6). Certification is required. Batteries shall be compatible \ 1011 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1012 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1013 accurate within +0/-5% of the actual state of charge for the battery \ 1014 under test throughout the discharge. Manufacturer and battery data \ 1015 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1016 logic circuitry. Pull-up resistors will be provided by the charger. \ 1017 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1018 Smart batteries may act as master or slave on the bus, but must \ 1019 perform bus master timing arbitration according to the SMBus \ 1020 specification when acting as master. | 1021 """ 1022 time.sleep(30) 1023 1024 at_rate_register = SMBusReg.AT_RATE 1025 at_rate_time_to_full_register = SMBusReg.AT_RATE_TIME_TO_FULL 1026 at_rate_time_to_empty_register = SMBusReg.AT_RATE_TIME_TO_EMPTY 1027 at_rate_ok_register = SMBusReg.AT_RATE_OK 1028 # 1029 failed_tests = [] 1030 1031 # 1032 # # Charging(mA) 1033 logger.write_info_to_report("Setting AtRate value to 1000(mA)") 1034 _smbus.write_register(at_rate_register, 1000) 1035 time.sleep(2) 1036 at_rate_response = _smbus.read_register(at_rate_register) 1037 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1038 1039 full_charge = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1040 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1041 calculated_time_to_full = (full_charge[0] - remaining_capacity[0]) / 1000 * 60 / 0.975 1042 1043 charging_elems = [ 1044 {"register": at_rate_time_to_full_register, "requirement": calculated_time_to_full}, 1045 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1046 {"register": at_rate_ok_register, "requirement": True}, 1047 ] 1048 1049 failed_tests = analyze_register_response(charging_elems, failed_tests, at_rate_response[0]) 1050 1051 # # Discharging 1052 logger.write_info_to_report("Setting AtRate value to -1000(mA)") 1053 _smbus.write_register(at_rate_register, -1000) 1054 time.sleep(2) 1055 at_rate_response = _smbus.read_register(at_rate_register) 1056 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1057 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1058 empty_requirement = remaining_capacity[0] / 1000 * 60 * 0.975 1059 1060 discharging_elems = [ 1061 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1062 {"register": at_rate_time_to_empty_register, "requirement": empty_requirement}, 1063 ] 1064 1065 failed_tests = analyze_register_response(discharging_elems, failed_tests, at_rate_response[0]) 1066 1067 # AtRateOK -- Charging & Discharging 1068 rates = [ 1069 {"charge": True, "rate": 2}, 1070 {"charge": True, "rate": 0.1}, 1071 {"charge": False, "rate": 2}, 1072 {"charge": False, "rate": 0.1}, 1073 ] 1074 1075 for elem in rates: 1076 if elem["charge"]: 1077 logger.write_info_to_report(f"Charging battery at {elem['rate']}A") 1078 with _bms.charger(16.8, elem["rate"]): 1079 time.sleep(2) 1080 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1081 if not at_rate_ok_response[0]: 1082 message = ( 1083 f"Invalid response for {at_rate_ok_register.fname}. " 1084 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1085 ) 1086 logger.write_warning_to_report(message) 1087 failed_tests.append( 1088 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1089 ) 1090 else: 1091 logger.write_result_to_html_report( 1092 f"{at_rate_ok_register.fname} had expected value of True after charging at {elem['rate']}A" 1093 ) 1094 else: 1095 logger.write_info_to_report(f"Discharging battery at {elem['rate']}A") 1096 with _bms.load(elem["rate"]): 1097 time.sleep(2) 1098 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1099 if not at_rate_ok_response[0]: 1100 message = ( 1101 f"Invalid response for {at_rate_ok_register.fname}. " 1102 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1103 ) 1104 logger.write_warning_to_report(message) 1105 failed_tests.append( 1106 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1107 ) 1108 else: 1109 logger.write_result_to_html_report( 1110 f"{at_rate_ok_register.fname} had expected value of True " 1111 f"after discharging at {elem['rate']}A" 1112 ) 1113 # Rest(mA) 1114 logger.write_info_to_report("Setting AtRate value to 0(mA)") 1115 _smbus.write_register(at_rate_register, 0) 1116 time.sleep(2) 1117 1118 at_rate_response = _smbus.read_register(at_rate_register) 1119 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1120 full_charge_three = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1121 logger.write_info_to_report(f"{SMBusReg.FULL_CHARGE_CAPACITY.fname}: {full_charge_three}") 1122 1123 remaining_capacity_three = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1124 logger.write_info_to_report(f"{SMBusReg.REMAINING_CAPACITY.fname}: {remaining_capacity_three}") 1125 resting_elems = [ 1126 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1127 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1128 {"register": at_rate_ok_register, "requirement": True}, 1129 ] 1130 1131 failed_tests = analyze_register_response(resting_elems, failed_tests, at_rate_response[0]) 1132 1133 # Power Mode 1134 battery_mode_register = SMBusReg.BATTERY_MODE 1135 writing_value = 0x8000 1136 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1137 _smbus.write_register(battery_mode_register, writing_value) 1138 batt_response = _smbus.read_register(battery_mode_register) 1139 logger.write_info_to_report(f"{battery_mode_register.fname} after writing {writing_value}: {batt_response}") 1140 batt_bytes = batt_response[1] 1141 batt_analyzed = BatteryMode(batt_bytes) 1142 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1143 1144 writing_value = 0x8080 1145 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1146 _smbus.write_register(battery_mode_register, writing_value) 1147 1148 batt_mode_response = _smbus.read_register(battery_mode_register) 1149 logger.write_info_to_report( 1150 f"{battery_mode_register.fname} after writing {writing_value}: {batt_mode_response}" 1151 ) 1152 batt_bytes = batt_mode_response[1] 1153 batt_analyzed = BatteryMode(batt_bytes) 1154 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1155 if batt_analyzed.capacity_mode is not True: 1156 logger.write_warning_to_report( 1157 f"{battery_mode_register.fname} did not have Capacity Mode with expected " 1158 f"value of True, instead received {batt_analyzed.capacity_mode}" 1159 ) 1160 failed_tests.append(f"{battery_mode_register.fname} did not have expected Capacity Mode value") 1161 else: 1162 logger.write_result_to_html_report( 1163 f"{battery_mode_register.fname} had Capacity Mode with expected value of {batt_analyzed.capacity_mode}" 1164 ) 1165 1166 if failed_tests: 1167 message = f"{len(failed_tests)} AtRate tests failed: {', '.join(failed_tests)}" 1168 logger.write_warning_to_report(message) 1169 pytest.fail(f"<font color='#990000'>{message}</font>") 1170 1171 logger.write_result_to_html_report("All AtRate SMBus tests passed") 1172 1173 1174@pytest.mark.parametrize("reset_test_environment", [{"soc": 0.50}], indirect=True) 1175class TestConstantSMBusValues: 1176 """Test constant SMBus values""" 1177 1178 def test_constant_smbus_values(self): 1179 """ 1180 | Description | Constant SMBus Values | 1181 | :------------------- | :--------------------------------------------------------------------- | 1182 | GitHub Issue | turnaroundfactor/HITL#385 | 1183 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1184 jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1185 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1186 | Instructions | 1. Read Device Chemistry [0x22] </br>\ 1187 2. Read Device Name [0x21] </br>\ 1188 3. Read Manufacturer Name [0x20] </br>\ 1189 4. Read Serial Number [0x1C] </br>\ 1190 5. Read Manufacturer Date [0x1B] </br>\ 1191 6. Read Specification Info [0x1A] </br>\ 1192 7. Read Design Voltage [0x19] </br>\ 1193 8. Read Design Capacity [0x18] </br>\ 1194 9. Read Charging Voltage [0x15] </br>\ 1195 10. Read Charging Current [0x14] </br>\ 1196 11. Read Manufacturer Access [0x00] </br>\ 1197 12. Read Remaining Time Alarm [0x02] </br>\ 1198 13. Read Remaining Capacity Alarm [0x01] </br>\ 1199 14. Read AtRateOk [0x07] </br>\ 1200 15. Read AtRateTimeToEmpty [0x06] </br>\ 1201 16. Read AtRateTimeToFull [0x05] </br>\ 1202 17. Read AtRate [0x04] </br>\ 1203 18. Read Battery Mode [0x03] </br>\ 1204 19. Read Max Error [0x0C] </br>\ 1205 20. Read Full Charge Capacity [0x10] | 1206 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1207 ⦁ Expect Device Chemistry [0x22] to be LION </br>\ 1208 ⦁ Expect Device Name [0x21] to be BB-2590/U </br>\ 1209 ⦁ Expect Manufacturer Name [0x20] to be TURN-AROUND FACTOR </br>\ 1210 ⦁ Expect Serial Number [0x1C] to be a unique value </br>\ 1211 ⦁ Expect Manufacturer Date [0x1B] to be 0x0100 </br>\ 1212 ⦁ Expect Specification Info [0x1A] to be 0x0100 </br>\ 1213 ⦁ Expect Design Voltage [0x19] to be 16800 </br>\ 1214 ⦁ Expect Design Capacity [0x18] to be CAPACITY * 0.975 </br>\ 1215 ⦁ Expect Charging Voltage [0x15] to be 16800 </br>\ 1216 ⦁ Expect Charging Current [0x14] to be 2000 </br>\ 1217 ⦁ Expect Manufacturer Access [0x00] to be 0 </br>\ 1218 ⦁ Expect Remaining Time Alarm [0x02] to be 10 </br>\ 1219 ⦁ Expect Remaining Capacity Alarm [0x01] to be SOC * Design Capacity </br>\ 1220 ⦁ Expect AtRateOk [0x07] to be True </br>\ 1221 ⦁ Expect AtRateTimeToEmpty [0x06] to be 65535 </br>\ 1222 ⦁ Expect AtRateTimeToFull [0x05] to be 65535 </br>\ 1223 ⦁ Expect AtRate [0x04] to be 0 </br>\ 1224 ⦁ Expect Battery Mode [0x03] to be 0 </br>\ 1225 ⦁ Expect Max Error [0x0C] to be 0 </br>\ 1226 ⦁ Expect Full Charge Capacity [0x10] to be CAPACITY | 1227 | Estimated Duration | 10 seconds | 1228 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1229 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1230 Data (SBData) Specification, version 1.1, with the exception that \ 1231 SBData safety signal hardware requirements therein shall be replaced \ 1232 with a charge enable when a charge enable is specified (see 3.1 and \ 1233 3.5.6). Certification is required. Batteries shall be compatible \ 1234 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1235 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1236 accurate within +0/-5% of the actual state of charge for the battery \ 1237 under test throughout the discharge. Manufacturer and battery data \ 1238 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1239 logic circuitry. Pull-up resistors will be provided by the charger. \ 1240 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1241 Smart batteries may act as master or slave on the bus, but must \ 1242 perform bus master timing arbitration according to the SMBus \ 1243 specification when acting as master. | 1244 """ 1245 constant_elements = [ 1246 {"register": SMBusReg.DEVICE_CHEMISTRY, "requirement": "LION"}, 1247 {"register": SMBusReg.DEVICE_NAME, "requirement": "BB-2590/U"}, 1248 {"register": SMBusReg.MANUFACTURER_NAME, "requirement": "TURN-AROUND FACTOR"}, 1249 {"register": SMBusReg.SERIAL_NUM, "requirement": 0xFFFF}, 1250 {"register": SMBusReg.MANUFACTURER_DATE, "requirement": None}, 1251 {"register": SMBusReg.SPECIFICATION_INFO, "requirement": None}, 1252 {"register": SMBusReg.DESIGN_VOLTAGE, "requirement": 16800}, 1253 { 1254 "register": SMBusReg.DESIGN_CAPACITY, 1255 "requirement": math.floor(0.975 * CELL_CAPACITY_AH * 1000), 1256 }, 1257 {"register": SMBusReg.CHARGING_VOLTAGE, "requirement": 16800}, 1258 {"register": SMBusReg.CHARGING_CURRENT, "requirement": 2000}, 1259 {"register": SMBusReg.MANUFACTURING_ACCESS, "requirement": 0}, 1260 {"register": SMBusReg.REMAINING_TIME_ALARM, "requirement": 10}, 1261 {"register": SMBusReg.REMAINING_CAPACITY, "requirement": 48}, 1262 {"register": SMBusReg.AT_RATE_OK, "requirement": True}, 1263 {"register": SMBusReg.AT_RATE_TIME_TO_EMPTY, "requirement": 65535}, 1264 {"register": SMBusReg.AT_RATE_TIME_TO_FULL, "requirement": 65535}, 1265 {"register": SMBusReg.AT_RATE, "requirement": 0}, 1266 {"register": SMBusReg.BATTERY_MODE, "requirement": 0}, 1267 {"register": SMBusReg.MAX_ERROR, "requirement": 0}, 1268 { 1269 "register": SMBusReg.FULL_CHARGE_CAPACITY, 1270 "requirement": CELL_CAPACITY_AH * 1000, 1271 }, 1272 ] 1273 test_failed = False 1274 1275 for element in constant_elements: 1276 register = element["register"] 1277 requirement = element["requirement"] 1278 1279 read_response = _smbus.read_register(register) 1280 1281 if register == SMBusReg.SERIAL_NUM: 1282 if read_response[0] != requirement: 1283 logger.write_result_to_html_report( 1284 f"{register.fname} had unique value of {read_response[0]} ({read_response[1]})" 1285 ) 1286 else: 1287 logger.write_result_to_html_report( 1288 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1289 f"not a unique value</font>" 1290 ) 1291 test_failed = True 1292 continue 1293 1294 if register == SMBusReg.REMAINING_CAPACITY: 1295 design_capacity = _smbus.read_register(SMBusReg.DESIGN_CAPACITY) 1296 requirement = design_capacity[0] // 2 1297 if requirement - 3 <= read_response[0] <= requirement + 3: 1298 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1299 else: 1300 logger.write_result_to_html_report( 1301 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1302 f"not {requirement}</font>" 1303 ) 1304 test_failed = True 1305 1306 elif isinstance(requirement, int): 1307 if read_response[0] == requirement or read_response[1] == requirement.to_bytes(2, byteorder="little"): 1308 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1309 else: 1310 logger.write_result_to_html_report( 1311 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1312 f"not {requirement}</font>" 1313 ) 1314 test_failed = True 1315 elif read_response[0] == requirement: 1316 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1317 else: 1318 logger.write_result_to_html_report( 1319 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1320 f"not {requirement}</font>" 1321 ) 1322 test_failed = True 1323 1324 if test_failed: 1325 pytest.fail() 1326 1327 logger.write_result_to_html_report("All SMBus Constant Values passed") 1328 1329 1330class TestVariableSMBusValues: 1331 """Test variable SMBus values""" 1332 1333 class TestCycleCount(CSVRecordEvent): 1334 """@private Compare SMBus Cycle Count to reported count""" 1335 1336 cycle_count = 0 1337 required_value = None 1338 1339 @classmethod 1340 def failed(cls) -> bool: 1341 """Check if test parameters were exceeded""" 1342 return bool(cls.cycle_count != cls.required_value) 1343 1344 @classmethod 1345 def verify(cls, row, _serial_data, _cell_data): 1346 """Set Cycle count value""" 1347 cls.cycle_count = row["Cycle Count"] 1348 if cls.required_value is None: 1349 cls.required_value = cls.cycle_count + 1 1350 1351 @classmethod 1352 def result(cls): 1353 """Detailed test result information""" 1354 return f"Cycle Count: {cls.cmp(cls.cycle_count, '==', cls.required_value or 0, form='d')}" 1355 1356 class TestCurrent(CSVRecordEvent): 1357 """@private Compare SMBus Current value to expected value""" 1358 1359 allowable_error = 0.015 1360 smbus_data = SimpleNamespace(current=0, terminal=0, error=0.0) 1361 1362 @classmethod 1363 def failed(cls) -> bool: 1364 """Check if test parameters were exceeded""" 1365 return bool(cls.smbus_data.error > cls.allowable_error) 1366 1367 @classmethod 1368 def verify(cls, row, _serial_data, _cell_data): 1369 """Set current and expected values""" 1370 row_data = SimpleNamespace(current=row["Current (mA)"], terminal=row["HITL Current (A)"] * 1000) 1371 row_data.error = abs((row_data.current - row_data.terminal) / row_data.terminal) 1372 if abs(row_data.current) > 100: 1373 cls.smbus_data = max(cls.smbus_data, row_data, key=lambda data: data.error) 1374 1375 @classmethod 1376 def result(cls): 1377 """Detailed test result information""" 1378 return ( 1379 f"Current error: {cls.cmp(cls.smbus_data.error, '<=', cls.allowable_error)} " 1380 f"(SMBus Current: {cls.smbus_data.current} mA, HITL Terminal Current: {cls.smbus_data.terminal} mA)" 1381 ) 1382 1383 class TestVoltage(CSVRecordEvent): 1384 """@private Compare HITL Terminal Voltage to reported SMBus voltage""" 1385 1386 allowable_error = 0.015 1387 smbus_data = SimpleNamespace(voltage=0, terminal=0, error=0.0) 1388 1389 @classmethod 1390 def failed(cls) -> bool: 1391 """Check if test parameters were exceeded""" 1392 return bool(cls.smbus_data.error > cls.allowable_error) 1393 1394 @classmethod 1395 def verify(cls, row, _serial_data, _cell_data): 1396 """Voltage within range""" 1397 row_data = SimpleNamespace(voltage=row["Voltage (mV)"], terminal=row["HITL Voltage (V)"] * 1000) 1398 row_data.error = abs((row_data.voltage - row_data.terminal) / row_data.terminal) 1399 cls.smbus_data = max(cls.smbus_data, row_data, key=lambda data: data.error) 1400 1401 @classmethod 1402 def result(cls): 1403 """Detailed test result information.""" 1404 return ( 1405 f"Voltage error: {cls.cmp(cls.smbus_data.error, '<=', cls.allowable_error)} " 1406 f"(SMBus Voltage: {cls.smbus_data.voltage} mV, HITL Terminal Voltage: {cls.smbus_data.terminal} mV)" 1407 ) 1408 1409 class TestTemperature(CSVRecordEvent): 1410 """@private Compare average HITL THERM1 & THERM2 temperatures to reported SMBus Temperature""" 1411 1412 smbus_temperature = 0 1413 average_hitl_temperature = 0 1414 low_temperature_range = 0 1415 high_temperature_range = 0 1416 1417 @classmethod 1418 def failed(cls) -> bool: 1419 """Check if test parameters were exceeded""" 1420 return (cls.smbus_temperature <= cls.low_temperature_range) or ( 1421 cls.smbus_temperature >= cls.high_temperature_range 1422 ) 1423 1424 @classmethod 1425 def verify(cls, row, _serial_data, _cell_data): 1426 """Voltage within range""" 1427 cls.smbus_temperature = row["Temperature (dK)"] / 10 - 273 1428 cls.average_hitl_temperature = statistics.mean([_plateset.thermistor1, _plateset.thermistor2]) 1429 cls.low_temperature_range = cls.average_hitl_temperature - 5 1430 cls.high_temperature_range = cls.average_hitl_temperature + 5 1431 1432 @classmethod 1433 def result(cls): 1434 """Detailed test result information.""" 1435 return ( 1436 f"Temperature Error: " 1437 f"{cls.cmp(cls.smbus_temperature, '>=', cls.low_temperature_range, '°C', '.2f')}, " 1438 f" {cls.cmp(cls.smbus_temperature, '<=', cls.high_temperature_range, '°C', '.2f')}," 1439 f"(SMBus Temperature: {cls.smbus_temperature:.2f} °C, " 1440 f"HITL THERM1 & THERM2 Average Temperature: {cls.average_hitl_temperature:.2f} °C)" 1441 ) 1442 1443 class TestCellVoltage1(CSVRecordEvent): 1444 """@private Compare HITL Cell Voltage 1 to reported Cell Voltage1""" 1445 1446 allowable_error = 0.01 1447 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1448 1449 @classmethod 1450 def failed(cls) -> bool: 1451 """Check if test parameters were exceeded""" 1452 return bool(cls.max.error > cls.allowable_error) 1453 1454 @classmethod 1455 def verify(cls, row, _serial_data, _cell_data): 1456 """Cell Voltage 1 within range""" 1457 row_data = SimpleNamespace( 1458 cell_voltage=row["Cell Voltage1 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 1 Volts (V)"]) * 1000) 1459 ) 1460 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1461 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1462 1463 @classmethod 1464 def result(cls): 1465 """Detailed test result information.""" 1466 return ( 1467 f"Cell Voltage1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1468 f"(SMBus Cell Voltage1: {cls.max.cell_voltage:.2f} mV, HITL Cell Sim 1 Voltage: " 1469 f"{cls.max.hitl_cell_voltage:.2f} mV)" 1470 ) 1471 1472 class TestCellVoltage2(CSVRecordEvent): 1473 """@private Compare HITL Cell Voltage 2 to reported Cell Voltage2""" 1474 1475 allowable_error = 0.01 1476 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1477 1478 @classmethod 1479 def failed(cls) -> bool: 1480 """Check if test parameters were exceeded""" 1481 return bool(cls.max.error > cls.allowable_error) 1482 1483 @classmethod 1484 def verify(cls, row, _serial_data, _cell_data): 1485 """Cell Voltage 2 within range""" 1486 row_data = SimpleNamespace( 1487 cell_voltage=row["Cell Voltage2 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 2 Volts (V)"]) * 1000) 1488 ) 1489 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1490 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1491 1492 @classmethod 1493 def result(cls): 1494 """Detailed test result information.""" 1495 return ( 1496 f"Cell Voltage2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1497 f"(SMBus Cell Voltage2: {cls.max.cell_voltage:.2f} mV, " 1498 f"HITL Cell Sim 2 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1499 ) 1500 1501 class TestCellVoltage3(CSVRecordEvent): 1502 """@private Compare HITL Cell Voltage 3 to reported Cell Voltage3""" 1503 1504 allowable_error = 0.01 1505 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1506 1507 @classmethod 1508 def failed(cls) -> bool: 1509 """Check if test parameters were exceeded""" 1510 return bool(cls.max.error > cls.allowable_error) 1511 1512 @classmethod 1513 def verify(cls, row, _serial_data, _cell_data): 1514 """Cell Voltage 3 within range""" 1515 row_data = SimpleNamespace( 1516 cell_voltage=row["Cell Voltage3 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 3 Volts (V)"]) * 1000) 1517 ) 1518 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1519 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1520 1521 @classmethod 1522 def result(cls): 1523 """Detailed test result information.""" 1524 return ( 1525 f"Cell Voltage3 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1526 f"(SMBus Cell Voltage3: {cls.max.cell_voltage:.2f} mV, " 1527 f"HITL Cell Sim 3 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1528 ) 1529 1530 class TestCellVoltage4(CSVRecordEvent): 1531 """@private Compare HITL Cell Voltage 4 to reported Cell Voltage4""" 1532 1533 allowable_error = 0.01 1534 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1535 1536 @classmethod 1537 def failed(cls) -> bool: 1538 """Check if test parameters were exceeded""" 1539 return bool(cls.max.error > cls.allowable_error) 1540 1541 @classmethod 1542 def verify(cls, row, _serial_data, _cell_data): 1543 """Cell Voltage 4 within range""" 1544 row_data = SimpleNamespace( 1545 cell_voltage=row["Cell Voltage4 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 4 Volts (V)"]) * 1000) 1546 ) 1547 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1548 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1549 1550 @classmethod 1551 def result(cls): 1552 """Detailed test result information.""" 1553 return ( 1554 f"Cell Voltage4 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1555 f"(SMBus Cell Voltage4: {cls.max.cell_voltage:.2f} mV, " 1556 f"HITL Cell Sim 4 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1557 ) 1558 1559 class TestStateOfCharge(CSVRecordEvent): 1560 """@private Compare HITL State of Charge to reported SMBus State of Charge""" 1561 1562 allowable_error = 5 1563 max = SimpleNamespace(absolute_charge=0, hitl_charge=0, error=0.0) 1564 1565 @classmethod 1566 def failed(cls) -> bool: 1567 """Check if test parameters were exceeded""" 1568 return bool(cls.max.error > cls.allowable_error) 1569 1570 @classmethod 1571 def verify(cls, row, _serial_data, _cell_data): 1572 """State of Charge within range""" 1573 row_data = SimpleNamespace(absolute_charge=row["Absolute State Of Charge (%)"]) 1574 row_data.hitl_charge = min( 1575 float(row["Cell Sim 1 SOC (%)"].replace("%", "")), 1576 float(row["Cell Sim 2 SOC (%)"].replace("%", "")), 1577 float(row["Cell Sim 3 SOC (%)"].replace("%", "")), 1578 float(row["Cell Sim 4 SOC (%)"].replace("%", "")), 1579 ) 1580 row_data.error = abs((row_data.absolute_charge - row_data.hitl_charge)) 1581 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1582 1583 @classmethod 1584 def result(cls): 1585 """Detailed test result information.""" 1586 return ( 1587 f"State of Charge error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '%', '.1f')}" 1588 f"(SMBus Absolute State of Charge: {cls.max.absolute_charge:.2f} %, " 1589 f"Lowest HITL State of Charge: {cls.max.hitl_charge:.2f} %)" 1590 ) 1591 1592 def test_variable_smbus_values(self): 1593 """ 1594 | Description | Variable SMBus Values | 1595 | :------------------- | :--------------------------------------------------------------------- | 1596 | GitHub Issue | turnaroundfactor/HITL#385 | 1597 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1598jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1599 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1600 | Instructions | 1. Set thermistors to 23°C </br>\ 1601 2. Put cells in a rested state at 2.5V per cell </br>\ 1602 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 1603 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours)</br>\ 1604 5. Discharge at 2A until battery reaches 10V then stop discharging | 1605 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1606 ⦁ Expect Cycle Count [0x17] to be 0 </br>\ 1607 ⦁ Expect Current [0x0A] to be within 1% for abs(Terminal Current > 100mA) </br>\ 1608 ⦁ Expect Voltage [0x09] to be HITL Terminal Voltage within 1% </br>\ 1609 ⦁ Expect Temperature [0x08] to be average of HITL THERM1 & THERM2 within 5°C </br>\ 1610 ⦁ Expect Cell Voltage1 [0x3C] to be HITL Cell Sim 1 Volts (V) </br>\ 1611 ⦁ Expect Cell Voltage2 [0x3D] to be HITL Cell Sim 2 Volts (V) </br>\ 1612 ⦁ Expect Cell Voltage3 [0x3E] to be HITL Cell Sim 3 Volts (V) </br>\ 1613 ⦁ Expect Cell Voltage4 [0x3F] to be HITL Cell Sim 4 Volts (V) </br>\ 1614 ⦁ Expect State of Charge [0x4F] to be HITL State of Charge | 1615 | Estimated Duration | 18 minutes | 1616 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1617 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1618 Data (SBData) Specification, version 1.1, with the exception that \ 1619 SBData safety signal hardware requirements therein shall be replaced \ 1620 with a charge enable when a charge enable is specified (see 3.1 and \ 1621 3.5.6). Certification is required. Batteries shall be compatible \ 1622 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1623 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1624 accurate within +0/-5% of the actual state of charge for the battery \ 1625 under test throughout the discharge. Manufacturer and battery data \ 1626 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1627 logic circuitry. Pull-up resistors will be provided by the charger. \ 1628 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1629 Smart batteries may act as master or slave on the bus, but must \ 1630 perform bus master timing arbitration according to the SMBus \ 1631 specification when acting as master. | 1632 """ 1633 1634 standard_charge() 1635 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 1636 standard_discharge() 1637 1638 # Check results 1639 if CSVRecordEvent.failed(): 1640 pytest.fail(CSVRecordEvent.result()) 1641 1642 1643@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 1644class TestChargeAcceptance(CSVRecordEvent): 1645 """Run a test for charge acceptance""" 1646 1647 def test_charge_acceptance(self): 1648 """ 1649 | Description | Charge Acceptance | 1650 | :------------------- | :--------------------------------------------------------------------- | 1651 | GitHub Issue | turnaroundfactor/HITL#517 | 1652 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1653jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D29) | 1654 | MIL-PRF Sections | 3.5.10.1 (Charge Acceptance) | 1655 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1656 2. Put cells in rested state at 4.2V per cell </br>\ 1657 3. Set THERM1 and THERM2 to -20°C </br>\ 1658 4. Attempt to charge at 1A </br>\ 1659 5. Attempt to discharge at 1A </br>\ 1660 6. Set THERM1 and THERM2 to 50°C </br>\ 1661 7. Attempt to charge at 1A </br>\ 1662 8. Attempt to discharge at 1A | 1663 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -20°C +/- 1.1°C </br>\ 1664 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1665 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA </br>\ 1666 ⦁ Expect Serial THERM1 & THERM 2 to be 50°C +/- 1.1°C </br>\ 1667 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1668 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 1669 | Estimated Duration | 17 seconds | 1670 | Note | When tested as specified in 4.7.2.9, batteries shall meet the \ 1671 capacity requirements 3.5.3 and the visual mechanical requirements \ 1672 of TABLE XIV. The surface temperature of the battery shall not \ 1673 exceed 185°F (85°C) | 1674 """ 1675 1676 failed_tests = [] 1677 temperatures = [-20, 50] 1678 1679 for set_temp in temperatures: 1680 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 1681 1682 _plateset.disengage_safety_protocols = True 1683 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 1684 _plateset.disengage_safety_protocols = False 1685 1686 time.sleep(2) 1687 1688 # Get the serial data 1689 serial_data = serial_monitor.read() 1690 1691 # Convert temperature to Celsius from Kelvin 1692 therm_one = serial_data["dk_temp"] / 10 - 273 1693 therm_two = serial_data["dk_temp1"] / 10 - 273 1694 temp_range = f"{set_temp}°C +/- 1.1°C" 1695 low_range = set_temp - 1.1 1696 high_range = set_temp + 1.1 1697 1698 if low_range <= therm_one <= high_range: 1699 logger.write_result_to_html_report( 1700 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 1701 ) 1702 else: 1703 logger.write_result_to_html_report( 1704 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1705 f"of {temp_range}</font>" 1706 ) 1707 failed_tests.append("THERM1") 1708 1709 if low_range <= therm_two <= high_range: 1710 logger.write_result_to_html_report( 1711 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 1712 ) 1713 else: 1714 logger.write_result_to_html_report( 1715 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1716 f"expected range of {temp_range}</font>" 1717 ) 1718 failed_tests.append("THERM2") 1719 1720 logger.write_info_to_report("Attempting to charge at 1A") 1721 limit = 0.03 1722 with _bms.charger(16.8, 1): 1723 time.sleep(1) 1724 charger_amps = _bms.charger.amps 1725 low_range = 1 - limit 1726 high_range = 1 + limit 1727 expected_current_range = f"1A +/- {limit}A" 1728 if low_range <= charger_amps <= high_range: 1729 logger.write_result_to_html_report( 1730 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 1731 f"expected range of {expected_current_range}" 1732 ) 1733 else: 1734 logger.write_result_to_html_report( 1735 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 1736 f"which was not within the expected range of {expected_current_range} </font>" 1737 ) 1738 failed_tests.append("HITL Terminal Current") 1739 1740 logger.write_info_to_report("Attempting to discharge at 1A") 1741 with _bms.load(1): 1742 time.sleep(1) 1743 load_amps = -1 * _bms.load.amps 1744 low_range = -1 - limit 1745 high_range = -1 + limit 1746 expected_current_range = f"-1A +/- {limit}A" 1747 if low_range <= load_amps <= high_range: 1748 logger.write_result_to_html_report( 1749 f"HITL Terminal Current was {load_amps:.3f}A after discharging, which was within the " 1750 f"expected range of {expected_current_range}" 1751 ) 1752 else: 1753 logger.write_result_to_html_report( 1754 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A after discharging, ' 1755 f"which was not within the expected range of {expected_current_range} </font>" 1756 ) 1757 failed_tests.append("HITL Terminal Current") 1758 1759 if len(failed_tests) > 0: 1760 pytest.fail() 1761 1762 logger.write_result_to_html_report("All checks passed test") 1763 1764 1765@pytest.mark.parametrize("reset_test_environment", [{"volts": 4.2}], indirect=True) 1766class TestHighTemperaturePermanentCutoff(CSVRecordEvent): 1767 """Run a test for High temperature permanent cutoff""" 1768 1769 def test_high_temp_perm_cutoff(self): 1770 """ 1771 | Description | High Temperature Permanent Cutoff | 1772 | :------------------- | :--------------------------------------------------------------------- | 1773 | GitHub Issue | turnaroundfactor/HITL#516 | 1774 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1775jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 1776 | MIL-PRF Sections | 3.7.2.5 (High Temperature permanent cut off devices) | 1777 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1778 2. Put cells in rested state at 4.2V per cell </br>\ 1779 3. Set THERM1 and THERM2 to 95°C </br>\ 1780 4. Measure Voltage </br>\ 1781 5. Attempt to charge at 1A </br>\ 1782 6. Attempt to discharge at 1A </br>\ 1783 7. Set temperature to 40°C </br>\ 1784 8. Measure Voltage | 1785 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 95°C +/- 5°C </br>\ 1786 ⦁ Expect Overtemperature Permanent Disable flag to be set </br>\ 1787 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V </br>\ 1788 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1789 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1790 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V | 1791 | Estimated Duration | 6 seconds | 1792 | Note | The following test shall be performed. Charge batteries as specified \ 1793 in 4.6; use of 4.6.3 is permitted. Each battery shall be permanently \ 1794 shut off when the temperature of the battery reaches 199 ± 9°F \ 1795 (93 ± 5°C). The device shall prevent charging and discharging of the \ 1796 battery. The minimum quantity shall be as specified in the applicable \ 1797 specification sheet. When tested as specified in 4.7.4.10, battery \ 1798 voltage shall be zero volts after high temperature storage and shall \ 1799 remain at zero after 104°F (40°C) storage. | 1800 """ 1801 1802 failed_tests = [] 1803 timeout_seconds = 30 1804 1805 logger.write_info_to_report("Setting THERM1 & THERM2 to 95°C") 1806 _plateset.disengage_safety_protocols = True 1807 _plateset.thermistor1 = _plateset.thermistor2 = 95 1808 _plateset.disengage_safety_protocols = False 1809 1810 time.sleep(1) 1811 serial_data = serial_monitor.read() # Get the serial data 1812 # Convert temperature to Celsius from Kelvin 1813 therm_one = serial_data["dk_temp"] / 10 - 273 1814 therm_two = serial_data["dk_temp1"] / 10 - 273 1815 1816 if 90 <= therm_one <= 100: 1817 logger.write_result_to_html_report( 1818 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1819 ) 1820 else: 1821 logger.write_result_to_html_report( 1822 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1823 f"of 95°C +/- 5°C</font>" 1824 ) 1825 failed_tests.append("THERM1") 1826 1827 if 90 <= therm_two <= 100: 1828 logger.write_result_to_html_report( 1829 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1830 ) 1831 else: 1832 logger.write_result_to_html_report( 1833 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1834 f"expected range of 95°C +/- 5°C</font>" 1835 ) 1836 failed_tests.append("THERM2") 1837 1838 start = time.perf_counter() 1839 while (serial_data := serial_monitor.read()) and not serial_data["flags.permanentdisable_overtemp"]: 1840 if time.perf_counter() - start > timeout_seconds: 1841 message = f"Over-temperature permanent disable was not raised after {timeout_seconds} seconds." 1842 logger.write_failure_to_html_report(message) 1843 failed_tests.append("Over-temperature permanent disable flag.") 1844 break 1845 else: 1846 logger.write_result_to_html_report("Over-temperature permanent disable was properly set.") 1847 1848 logger.write_info_to_report("Measuring voltage...") 1849 time.sleep(1) 1850 1851 volt_range = "0V (-0.2V / +3V)" 1852 1853 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1854 if not -0.2 <= _bms.dmm.volts <= 3: 1855 logger.write_failure_to_html_report( 1856 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1857 f"which was not within the expected range of {volt_range}" 1858 ) 1859 failed_tests.append("HITL Terminal Voltage after temperature was set to 95°C") 1860 else: 1861 logger.write_result_to_html_report( 1862 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1863 f"which was within the expected range of {volt_range}" 1864 ) 1865 1866 logger.write_info_to_report("Attempting to charge at 1A") 1867 with _bms.charger(16.8, 1.0): 1868 time.sleep(1) 1869 if -0.020 <= _bms.charger.amps <= 0.020: 1870 logger.write_result_to_html_report( 1871 f"HITL Terminal Current was {_bms.charger.amps:.3f}A, which was within the " 1872 f"expected range of 0A +/- 20mA" 1873 ) 1874 else: 1875 logger.write_result_to_html_report( 1876 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A, which was' 1877 f" not within the expected range of 0A +/- 20mA </font>" 1878 ) 1879 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 1880 1881 logger.write_info_to_report("Attempting to discharge at 1A") 1882 1883 with _bms.load(1): 1884 time.sleep(1) 1885 if -0.020 <= _bms.load.amps <= 0.020: 1886 logger.write_result_to_html_report( 1887 f"HITL Terminal Current was {_bms.load.amps:.3f}A, which was within the " 1888 f"expected range of 0A +/- 20mA" 1889 ) 1890 else: 1891 logger.write_result_to_html_report( 1892 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A, ' 1893 f"which was not within the expected range of 0A +/- 20mA </font>" 1894 ) 1895 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 1896 1897 logger.write_info_to_report("Setting Temperature to 40°C") 1898 _plateset.thermistor1 = _plateset.thermistor2 = 40 1899 1900 logger.write_info_to_report("Measuring voltage...") 1901 time.sleep(1) 1902 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1903 if not -0.2 <= _bms.dmm.volts <= 3: 1904 logger.write_failure_to_html_report( 1905 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1906 f"which was not within the expected range of {volt_range}" 1907 ) 1908 failed_tests.append("HITL Terminal Voltage after temperature was set to 40°C") 1909 else: 1910 logger.write_result_to_html_report( 1911 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V," 1912 f"which was within the expected range of {volt_range}" 1913 ) 1914 1915 if len(failed_tests) > 0: 1916 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 1917 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 1918 pytest.fail(message) 1919 1920 logger.write_result_to_html_report("All checks passed test") 1921 1922 1923@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 1924class TestHighTemperatureTemporaryCutoff(CSVRecordEvent): 1925 """Run a test for High temperature temporary cutoff""" 1926 1927 def test_high_temperature_temp_cutoff(self): 1928 """ 1929 | Description | High Temperature Temporary Cutoff | 1930 | :------------------- | :--------------------------------------------------------------------- | 1931 | GitHub Issue | turnaroundfactor/HITL#517 | 1932 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1933jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D27) | 1934 | MIL-PRF Sections | 3.7.2.4 (High Temperature permanent cut off devices) | 1935 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1936 2. Put cells in rested state at 4.2V per cell </br>\ 1937 3. Set THERM1 and THERM2 to 75°C </br>\ 1938 4. Measure Voltage </br>\ 1939 5. Attempt to charge at 1A </br>\ 1940 6. Attempt to discharge at 1A </br>\ 1941 7. Set temperature to 20°C </br>\ 1942 8. Measure Voltage | 1943 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 75°C +/- 5°C </br>\ 1944 ⦁ Expect Overtemp charge & Overtemp discharge flags to be set </br>\ 1945 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V </br>\ 1946 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1947 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1948 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V | 1949 | Estimated Duration | 12 seconds | 1950 | Note | Each battery shall contain a minimum quantity of normally closed \ 1951 thermoswitches that shall open at 158 ± 9°F (70 ± 5°C) and close at \ 1952 122 ± 9°F (50 ± 5°C). Each thermoswitch shall make physical contact \ 1953 with not less than one cell. The minimum quantity shall be as \ 1954 specified (see 3.1). The quantity of thermoswitches shall be \ 1955 certified. When tested as specified in 4.7.4.9, battery voltage shall \ 1956 be zero volts after each high temperature storage and batteries shall \ 1957 meet the voltage requirement of 3.5.2 after cooling. After completion \ 1958 of the test the battery shall be able to meet the full discharge \ 1959 capacity requirement of 3.5.3 after full charge. | 1960 """ 1961 1962 failed_tests = [] 1963 timeout_seconds = 30 1964 1965 temperature = 75 1966 high_range = temperature + 5 1967 low_range = temperature - 5 1968 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {temperature}°C") 1969 _plateset.thermistor1 = _plateset.thermistor2 = temperature 1970 1971 time.sleep(1) 1972 serial_data = serial_monitor.read() # Get the serial data 1973 # Convert temperature to Celsius from Kelvin 1974 therm_one = serial_data["dk_temp"] / 10 - 273 1975 therm_two = serial_data["dk_temp1"] / 10 - 273 1976 temp_range_text = f"{temperature}°C +/- 5°C" 1977 1978 if low_range <= therm_one <= high_range: 1979 logger.write_result_to_html_report( 1980 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1981 ) 1982 else: 1983 logger.write_result_to_html_report( 1984 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1985 f"of {temp_range_text}</font>" 1986 ) 1987 failed_tests.append("THERM1") 1988 1989 if low_range <= therm_two <= high_range: 1990 logger.write_result_to_html_report( 1991 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1992 ) 1993 else: 1994 logger.write_result_to_html_report( 1995 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1996 f"expected range of {temp_range_text}</font>" 1997 ) 1998 failed_tests.append("THERM2") 1999 2000 start = time.perf_counter() 2001 while (serial_data := serial_monitor.read()) and not serial_data["flags.fault_overtemp_discharge"]: 2002 if time.perf_counter() - start > timeout_seconds: 2003 message = f"Over-temperature discharge flag was not raised after {timeout_seconds} seconds." 2004 logger.write_failure_to_html_report(message) 2005 failed_tests.append("Over-temperature discharge flag.") 2006 break 2007 else: 2008 logger.write_result_to_html_report("Over-temperature discharge flag was properly set.") 2009 2010 logger.write_info_to_report("Measuring voltage...") 2011 time.sleep(1) 2012 2013 low_range = -0.2 2014 high_range = 3 2015 volt_range_text = f"0V ({low_range}V / +{high_range}V)" 2016 2017 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2018 if not -0.2 <= _bms.dmm.volts <= 3: 2019 logger.write_failure_to_html_report( 2020 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2021 f"which was not within the expected range of {volt_range_text}" 2022 ) 2023 failed_tests.append("HITL Terminal Voltage") 2024 else: 2025 logger.write_result_to_html_report( 2026 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2027 f"which was within the expected range of {volt_range_text}" 2028 ) 2029 2030 logger.write_info_to_report("Attempting to charge at 1A") 2031 high_range = 0.020 2032 amp_range_text = "0A +/- 20mA" 2033 with _bms.charger(16.8, 1.0): 2034 time.sleep(1) 2035 if (high_range * -1) <= _bms.charger.amps <= high_range: 2036 logger.write_result_to_html_report( 2037 f"HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, which was within the " 2038 f"expected range of {amp_range_text}" 2039 ) 2040 else: 2041 logger.write_result_to_html_report( 2042 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, ' 2043 f"which was not within the expected range of {amp_range_text} </font>" 2044 ) 2045 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 2046 2047 logger.write_info_to_report("Attempting to discharge at 1A") 2048 2049 with _bms.load(1): 2050 time.sleep(1) 2051 if (high_range * -1) <= _bms.load.amps <= high_range: 2052 logger.write_result_to_html_report( 2053 f"HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, which was within the " 2054 f"expected range of {amp_range_text}" 2055 ) 2056 else: 2057 logger.write_result_to_html_report( 2058 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, ' 2059 f"which was not within the expected range of {amp_range_text} </font>" 2060 ) 2061 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2062 2063 temperature = 20 2064 logger.write_info_to_report(f"Setting Temperature to {temperature}°C") 2065 _plateset.thermistor1 = _plateset.thermistor2 = temperature 2066 2067 logger.write_info_to_report("Measuring voltage...") 2068 time.sleep(1) 2069 high_range = 17 2070 low_range = 9 2071 if not (_plateset.load_switch or _plateset.charger_switch): 2072 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2073 if not low_range <= _bms.dmm.volts <= high_range: 2074 logger.write_failure_to_html_report( 2075 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2076 f"which was not within the expected range of {low_range}V to {high_range}V" 2077 ) 2078 failed_tests.append("HITL Terminal Voltage") 2079 else: 2080 logger.write_result_to_html_report( 2081 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2082 f"which was within expected range of {low_range}V to {high_range}V" 2083 ) 2084 else: 2085 if not low_range <= _bms.dmm.volts <= high_range: 2086 logger.write_failure_to_html_report( 2087 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, which was not within the " 2088 f"expected range of {low_range}V to {high_range}V" 2089 ) 2090 failed_tests.append("HITL Terminal Voltage") 2091 else: 2092 logger.write_result_to_html_report( 2093 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2094 f"which was within expected range of {low_range}V to {high_range}V" 2095 ) 2096 2097 if len(failed_tests) > 0: 2098 pytest.fail() 2099 2100 logger.write_result_to_html_report("All checks passed test") 2101 2102 2103@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 2104class TestExtremeLowTempDischarge(CSVRecordEvent): 2105 """Run a test for extreme low temperature discharge""" 2106 2107 def test_extreme_low_temp_discharge(self): 2108 """ 2109 | Description | Extreme low temperature discharge | 2110 | :------------------- | :--------------------------------------------------------------------- | 2111 | GitHub Issue | turnaroundfactor/HITL#518 | 2112 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2113jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 2114 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature discharge) | 2115 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2116 2. Put cells in rested state at 4.2V per cell </br>\ 2117 3. Set THERM1 and THERM2 to -30°C </br>\ 2118 4. Attempt to discharge at 1A | 2119 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -30°C +/- 1.1°C </br>\ 2120 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2121 | Estimated Duration | 6 seconds | 2122 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2123 Charge batteries in accordance with 4.6; use of 4.6.3 is not permitted.\ 2124 Store the batteries at -22 ± 2°F (-30 ± 1.1°C) for a minimum of \ 2125 4 hours. Discharge under these conditions at the rate specified to the \ 2126 specified cutoff voltage (see 3.1). After testing, batteries shall \ 2127 meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2128 """ 2129 2130 failed_tests = [] 2131 2132 logger.write_info_to_report("Setting THERM1 & THERM2 to -30°C") 2133 _plateset.disengage_safety_protocols = True 2134 _plateset.thermistor1 = _plateset.thermistor2 = -30 2135 _plateset.disengage_safety_protocols = False 2136 2137 # Get the serial data 2138 serial_data = serial_monitor.read() 2139 2140 # Convert temperature to Celsius from Kelvin 2141 therm_one = serial_data["dk_temp"] / 10 - 273 2142 therm_two = serial_data["dk_temp1"] / 10 - 273 2143 temp_range = "-30°C +/- 1.1°C" 2144 2145 if -31.1 <= therm_one <= -28.9: 2146 logger.write_result_to_html_report( 2147 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2148 ) 2149 else: 2150 logger.write_result_to_html_report( 2151 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2152 f"of {temp_range}</font>" 2153 ) 2154 failed_tests.append("THERM1 after setting temperature to -30°C") 2155 2156 if -31.1 <= therm_two <= -28.9: 2157 logger.write_result_to_html_report( 2158 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2159 ) 2160 else: 2161 logger.write_result_to_html_report( 2162 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2163 f"expected range of {temp_range}</font>" 2164 ) 2165 failed_tests.append("THERM2 after setting temperature to -30°C") 2166 2167 logger.write_info_to_report("Attempting to discharge at 1A") 2168 2169 with _bms.load(1): 2170 time.sleep(1) 2171 load_amps = -1 * _bms.load.amps 2172 expected_current_range = "-1A +/- 30mA" 2173 if -1.03 <= load_amps <= -0.97: 2174 logger.write_result_to_html_report( 2175 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2176 f"expected range of {expected_current_range}" 2177 ) 2178 else: 2179 logger.write_result_to_html_report( 2180 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2181 f"which was not within the expected range of {expected_current_range} </font>" 2182 ) 2183 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2184 2185 if len(failed_tests) > 0: 2186 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2187 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2188 pytest.fail(message) 2189 2190 logger.write_result_to_html_report("All checks passed test") 2191 2192 2193@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 2194class TestExtremeHighTempDischarge(CSVRecordEvent): 2195 """Run a test for extreme high temperature discharge""" 2196 2197 def test_extreme_high_temp_discharge(self): 2198 """ 2199 | Description | Extreme high temperature discharge | 2200 | :------------------- | :--------------------------------------------------------------------- | 2201 | GitHub Issue | turnaroundfactor/HITL#519 | 2202 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2203jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D31) | 2204 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2205 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2206 2. Put cells in rested state at 4.2V per cell </br>\ 2207 3. Set THERM1 and THERM2 to 55°C </br>\ 2208 4. Attempt to discharge at 1A | 2209 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 55°C +/- 1.1°C </br>\ 2210 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2211 | Estimated Duration | 6 seconds | 2212 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2213 Charge batteries in accordance with 4.6; use of 4.6.3 is not \ 2214 permitted. Store the batteries at 131 ± 2°F (55 ± 1.1°C) for a \ 2215 minimum of 4 hours. Discharge under these conditions at the rate \ 2216 specified to the specified cutoff voltage (see 3.1). After testing, \ 2217 batteries shall meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2218 """ 2219 2220 failed_tests = [] 2221 set_temp = 55 2222 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 2223 _plateset.disengage_safety_protocols = True 2224 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 2225 _plateset.disengage_safety_protocols = False 2226 2227 # Get the serial data 2228 serial_data = serial_monitor.read() 2229 2230 # Convert temperature to Celsius from Kelvin 2231 therm_one = serial_data["dk_temp"] / 10 - 273 2232 therm_two = serial_data["dk_temp1"] / 10 - 273 2233 temp_range = f"{set_temp}°C +/- 1.1°C" 2234 low_range = set_temp - 1.1 2235 high_range = set_temp + 1.1 2236 2237 if low_range <= therm_one <= high_range: 2238 logger.write_result_to_html_report( 2239 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2240 ) 2241 else: 2242 logger.write_result_to_html_report( 2243 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2244 f"of {temp_range}</font>" 2245 ) 2246 failed_tests.append(f"THERM1 after setting temperature to {set_temp}°C") 2247 2248 if low_range <= therm_two <= high_range: 2249 logger.write_result_to_html_report( 2250 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2251 ) 2252 else: 2253 logger.write_result_to_html_report( 2254 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2255 f"expected range of {temp_range}</font>" 2256 ) 2257 failed_tests.append(f"THERM2 after setting temperature to {set_temp}°C") 2258 2259 logger.write_info_to_report("Attempting to discharge at 1A") 2260 2261 with _bms.load(1): 2262 time.sleep(1) 2263 load_amps = -1 * _bms.load.amps 2264 low_range = -1 - 0.03 2265 high_range = -1 + 0.03 2266 expected_current_range = "-1A +/- 30mA" 2267 if low_range <= load_amps <= high_range: 2268 logger.write_result_to_html_report( 2269 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2270 f"expected range of {expected_current_range}" 2271 ) 2272 else: 2273 logger.write_result_to_html_report( 2274 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2275 f"which was not within the expected range of {expected_current_range} </font>" 2276 ) 2277 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2278 2279 if len(failed_tests) > 0: 2280 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2281 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2282 pytest.fail(message) 2283 2284 logger.write_result_to_html_report("All checks passed test") 2285 2286 2287class TestStateTransitions: 2288 """Run a test for state transition""" 2289 2290 def test_fast_sample(self, serial_watcher: SerialWatcher): 2291 """ 2292 | Description | Enter and exit fast sample state | 2293 | :------------------- | :--------------------------------------------------------------------- | 2294 | GitHub Issue | turnaroundfactor/HITL#478 | 2295 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2296jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2297 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2298 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2299 2. Transition to fast sample by charging 50mA+ </br>\ 2300 3. Maintain 50mA+ and ensure we are still in fast sample after 5 minutes </br> 2301 4. Transition to slow sample by discharging less than -50mA </br>\ 2302 5. Transition back to fast sample by charging 50mA+ </br>\ 2303 6. Transition to deep slumber by charging/discharging in the range -50mA to 50mA </br> 2304 7. Transition back to fast sample by charging 50mA+ | 2305 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2306 | Estimated Duration | 5 minutes | 2307 | Note | The power consumption for all electronics within the battery shall \ 2308 be less than 350 micro-ampere average, per battery or independent \ 2309 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2310 """ 2311 logger.write_info_to_report("Testing Fast Sample") 2312 assert _bms.load and _bms.charger # Make sure hardware exists 2313 2314 logger.write_result_to_html_report("Slow sample") 2315 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2316 2317 logger.write_result_to_html_report("Slow sample -> Fast sample") 2318 with _bms.charger(16.8, 0.200): 2319 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2320 logger.write_result_to_html_report("Fast sample after 5 minutes") 2321 time.sleep(5.5 * 60) 2322 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2323 2324 logger.write_result_to_html_report("Fast sample -> Slow sample") 2325 with _bms.load(0.200): 2326 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2327 2328 logger.write_result_to_html_report("Slow sample -> Fast sample") 2329 with _bms.charger(16.8, 0.200): 2330 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2331 2332 logger.write_result_to_html_report("Fast sample -> Deep slumber") 2333 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=15 * 60) 2334 2335 def test_slow_sample(self, serial_watcher: SerialWatcher): 2336 """ 2337 | Description | Enter and exit slow sample state | 2338 | :------------------- | :--------------------------------------------------------------------- | 2339 | GitHub Issue | turnaroundfactor/HITL#478 | 2340 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2341jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2342 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2343 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2344 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2345 3. Transition back to slow sample by discharging less than -50mA </br>\ 2346 4. Test deep slumber timer (discharge) </br>\ 2347 5. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2348 6. Discharge less than -50mA for 2 minutes </br>\ 2349 7. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2350 20mA, ensuring it takes 5 minutes </br>\ 2351 8. Test deep slumber timer (charge) </br>\ 2352 9. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2353 10. Charge in the range 20mA to 50mA for 2 minutes (keeps the comparator on <\br>\ 2354 without entering fast sample) </br>\ 2355 11. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2356 20mA, ensuring it takes 5 minutes | 2357 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2358 | Estimated Duration | 5 minutes | 2359 | Note | The power consumption for all electronics within the battery shall \ 2360 be less than 350 micro-ampere average, per battery or independent \ 2361 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2362 """ 2363 logger.write_info_to_report("Testing Slow Sample") 2364 assert _bms.load and _bms.charger # Make sure hardware exists 2365 2366 logger.write_result_to_html_report("Slow sample") 2367 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2368 2369 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2370 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2371 2372 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2373 with _bms.load(0.200): 2374 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2375 2376 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (discharging)") 2377 serial_watcher.assert_measurements() 2378 time.sleep(2 * 60) 2379 with _bms.load(0.200): 2380 time.sleep(2 * 60) 2381 wait_start_time = time.perf_counter() 2382 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2383 if state_events := serial_watcher.events.get("BMS_State"): 2384 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2385 assert state_events[-1].time - wait_start_time >= 5 * 60 2386 2387 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2388 with _bms.load(0.200): 2389 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2390 2391 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (charging)") 2392 serial_watcher.assert_measurements() 2393 time.sleep(2 * 60) 2394 with _bms.charger(16.8, 0.040): 2395 time.sleep(2 * 60) 2396 wait_start_time = time.perf_counter() 2397 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2398 if state_events := serial_watcher.events.get("BMS_State"): 2399 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2400 assert state_events[-1].time - wait_start_time >= 5 * 60 2401 2402 logger.write_result_to_html_report("Slow sample state test passed") 2403 2404 def test_deep_slumber(self, serial_watcher: SerialWatcher): 2405 """ 2406 | Description | Enter and exit slow deep slumber state | 2407 | :------------------- | :--------------------------------------------------------------------- | 2408 | GitHub Issue | turnaroundfactor/HITL#478 | 2409 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2410jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2411 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2412 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2413 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2414 3. Transition back to slow sample by discharging less than -50mA </br>\ 2415 4. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2416 5. Transition back to slow sample by charging in the range 20mA to 40mA </br>\ 2417 6. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2418 7. Transition back to slow sample by setting charge enable low while resting | 2419 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2420 | Estimated Duration | 5 minutes | 2421 | Note | The power consumption for all electronics within the battery shall \ 2422 be less than 350 micro-ampere average, per battery or independent \ 2423 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2424 """ 2425 logger.write_info_to_report("Testing Deep slumber") 2426 assert _bms.load and _bms.charger # Make sure hardware exists 2427 2428 logger.write_result_to_html_report("Slow sample") 2429 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE, wait_time=5 * 60) 2430 2431 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2432 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2433 2434 logger.write_result_to_html_report("Deep slumber -> Slow sample (discharging)") 2435 with _bms.load(0.200): 2436 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2437 2438 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2439 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2440 2441 logger.write_result_to_html_report("Deep slumber -> Slow sample (charging)") 2442 with _bms.charger(16.8, 0.040): 2443 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2444 2445 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2446 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2447 2448 logger.write_result_to_html_report("Deep slumber -> Slow sample (CE pin)") 2449 _plateset.ce_switch = True 2450 try: 2451 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2452 finally: # Ensure ce pin is reset regardless of outcome 2453 _plateset.ce_switch = False 2454 2455 logger.write_result_to_html_report("Deep slumber state test passed") 2456 2457 2458@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 2459class TestVoltageAccuracy: 2460 """Run a test for voltage accuracy""" 2461 2462 class VoltageCellAccuracy(CSVRecordEvent): 2463 """@private Check if HITL cell sim voltage matches serial cell sim voltage within 1%""" 2464 2465 percent_closeness = 0.05 2466 max = SimpleNamespace(cell_id=0, sim_v=0, bms_v=0, error=0.0) 2467 2468 @classmethod 2469 def failed(cls) -> bool: 2470 """Check if test parameters were exceeded.""" 2471 return bool(cls.max.error > cls.percent_closeness) 2472 2473 @classmethod 2474 def verify(cls, row, serial_data, _cell_data): 2475 """Cell voltage within range""" 2476 for i, cell_id in enumerate(_bms.cells): 2477 row_data = SimpleNamespace( 2478 cell_id=cell_id, 2479 sim_v=row[f"ADC Plate Cell {cell_id} Voltage (V)"], 2480 bms_v=serial_data[f"mvolt_cell{'' if i == 0 else i}"] / 1000, 2481 ) 2482 row_data.error = abs((row_data.bms_v - row_data.sim_v) / row_data.sim_v) 2483 cls.max = max(cls.max, row_data, key=lambda data: data.error) 2484 2485 @classmethod 2486 def result(cls): 2487 """Detailed test result information.""" 2488 return ( 2489 f"Cell Voltage Error: {cls.cmp(cls.max.error, '<=', cls.percent_closeness)}" 2490 f"(Sim {cls.max.cell_id}: {cls.max.sim_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 2491 ) 2492 2493 class TerminalVoltageAccuracy(CSVRecordEvent): 2494 """@private Compare HITL voltage to reported Terminal voltage.""" 2495 2496 percent_closeness = 0.05 2497 max = SimpleNamespace(hitl_v=0, bms_v=0, error=0.0) 2498 2499 @classmethod 2500 def failed(cls) -> bool: 2501 """Check if test parameters were exceeded.""" 2502 return bool(cls.max.error > cls.percent_closeness) 2503 2504 @classmethod 2505 def verify(cls, row, serial_data, _cell_data): 2506 """Terminal voltage within range""" 2507 row_data = SimpleNamespace(hitl_v=row["HITL Voltage (V)"], bms_v=serial_data["mvolt_terminal"] / 1000) 2508 row_data.error = abs((row_data.bms_v - row_data.hitl_v) / row_data.hitl_v) 2509 cls.max = max(cls.max, row_data, key=lambda data: data.error) 2510 2511 @classmethod 2512 def result(cls): 2513 """Detailed test result information.""" 2514 return ( 2515 f"Terminal Voltage error: {cls.cmp(cls.max.error, '<=', cls.percent_closeness)} " 2516 f"(HITL: {cls.max.hitl_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 2517 ) 2518 2519 def test_voltage_accuracy(self): 2520 """ 2521 | Description | Test the cell voltage accuracy | 2522 | :------------------- | :--------------------------------------------------------------------- | 2523 | GitHub Issue | turnaroundfactor/HITL#399 | 2524 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 2525 4.7.2.14.3 (Accuracy During Discharge) | 2526 | Instructions | 1. Set thermistors to 23C </br>\ 2527 2. Put cells in a rested state at 2.5V per cell </br>\ 2528 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 2529 4. Increase cell voltages in 100 mV increments up to and including 4.2V </br>\ 2530 | Pass / Fail Criteria | ⦁ HITL cell sim voltage matches serial cell sim voltage to within 1% </br>\ 2531 ⦁ HITL voltage matches serial Terminal voltage to within 1% </br>\ 2532 | Estimated Duration | 12 hours | 2533 | Note | MIL-PRF 3.5.8.3 (Accuracy): The values of the display shall be \ 2534 accurate within +0/-5% of the actual values for the battery. \ 2535 (see 4.7.2.14.3). | 2536 """ 2537 _bms.timer.reset() # Keep track of runtime 2538 for target_mv in range(2500, 4300, 100): 2539 voltages = ", ".join(f"{cell.volts}V" for cell in _bms.cells.values()) 2540 logger.write_info_to_report(f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {voltages}, ") 2541 _bms.csv.cycle.record(_bms.timer.elapsed_time) 2542 for cell in _bms.cells.values(): 2543 cell.volts = target_mv / 1000 2544 time.sleep(3) 2545 2546 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 2547 pytest.fail(CSVRecordEvent.result()) 2548 2549 2550# @pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 2551@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.5}], indirect=True) 2552class TestSerialFaults: 2553 """Test all faults""" 2554 2555 def test_wakeup_1(self, serial_watcher: SerialWatcher): 2556 """ 2557 | Description | Test Wakeup 1 | 2558 | :------------------- | :--------------------------------------------------------------------- | 2559 | GitHub Issue | turnaroundfactor/HITL#476 | 2560 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2561jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2562 | MIL-PRF Sections | 3.6.8.2 (Battery wake up) | 2563 | Instructions | 1. Set Default state at 0mA </br>\ 2564 2. Discharge at 100 mA (below -46 mA) </br>\ 2565 3. Discharge at 10 mA (between -46mA and 19ma) </br>\ 2566 4. Charge at 100 mA, (above 19 mA) | 2567 | Pass / Fail Criteria | ⦁ n_wakeup_gpio = 1, with default state at 0 mA </br>\ 2568 ⦁ n_wakeup_gpio = 0, with default state at 100 mA </br>\ 2569 ⦁ n_wakeup_gpio = 1, with default state at 10 mA </br>\ 2570 ⦁ n_wakeup_gpio = 0, with default state at 0 mA | 2571 | Estimated Duration | 10 seconds | 2572 | Note | The BMS should be in a state of slumber if the current measured is \ 2573 between -46mA and 19ma. This is done using internal comparators on the \ 2574 board to a logical AND chip feeding into an interrupt pin. To test \ 2575 this, 3 different currents should be set. One current below -46ma, \ 2576 another current between -46mA and 19ma, and another current above \ 2577 19ma. If the current is within the allowable range, we should read \ 2578 logic 1 on the N_WAKEUP pin. If the current is outside (above or \ 2579 below) we should read logic 0 on the pin. | 2580 """ 2581 2582 logger.write_info_to_report("Testing Wakeup") 2583 assert _bms.load and _bms.charger # Confirm hardware is available 2584 2585 logger.write_result_to_html_report("Default state") 2586 serial_watcher.assert_true("n_wakeup_gpio", True, 1) 2587 2588 logger.write_result_to_html_report("Discharging 200 mA") 2589 with _bms.load(0.200): 2590 serial_watcher.assert_true("n_wakeup_gpio", False, 2) 2591 2592 logger.write_result_to_html_report("Discharging 10 mA") 2593 with _bms.load(0.010): 2594 serial_watcher.assert_true("n_wakeup_gpio", True, 3) 2595 2596 logger.write_result_to_html_report("Charging 200 mA") 2597 with _bms.charger(16.8, 0.200): 2598 serial_watcher.assert_true("n_wakeup_gpio", False, 4) 2599 2600 def test_overtemp_fault(self, serial_watcher: SerialWatcher): 2601 """ 2602 | Description | Test over temperature Fault | 2603 | :------------------- | :--------------------------------------------------------------------- | 2604 | GitHub Issue | turnaroundfactor/HITL#476 | 2605 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2606jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2607 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2608 | Instructions | 1. Set Default state at 15 </br>\ 2609 2. Resting at 59°C (at or above 59°C) </br>\ 2610 3. Discharging at 100 mA, 59°C (at or above 59°C) </br>\ 2611 4. Charging at 100 mA, 54°C (at or above 53°C) </br>\ 2612 5. Charging at 100 mA, 94°C (at or above 93°C) </br>\ 2613 6. Discharging at 100 mA, 94°C (at or above 93°C) </br>\ 2614 7. Rest at 94°C (at or above 93°C) | 2615 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2616 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2617 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2618 ⦁ Fault overtemp charge value is False (default state) </br>\ 2619 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2620 ⦁ Measure output fets disabled is False (default state) </br>\ 2621 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2622 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2623 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2624 ⦁ After resting permanent disable, permanent disable overtemp is True | 2625 | Estimated Duration | 12 hours | 2626 | Note | If our batteries get too hot, we must trigger a fault. This is \ 2627 common during high discharge or charging cycles. There are 3 different \ 2628 environments where we would trigger an overtemp fault: charge, \ 2629 discharge and resting. While charging, if we are above 53C we must \ 2630 trigger a fault If we are resting or discharging at 59 degrees we must \ 2631 trigger a fault. Both of these faults should trigger a prefault \ 2632 condition in our flags. After we cycle again we should then trigger a \ 2633 full fault. If the temperature ever goes above 93 degrees, the fault \ 2634 should never clear and we should be in permanent fault and trigger \ 2635 the fets. (This should also be seen in the flags) | 2636 """ 2637 logger.write_info_to_report("Testing Overtemp") 2638 assert _bms.load and _bms.charger # Confirm hardware is available 2639 2640 # Test default state 2641 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2642 _plateset.thermistor1 = 15 2643 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 1) 2644 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 1) 2645 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 2646 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 2647 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 2648 serial_watcher.assert_true("flags.measure_output_fets_disabled", False, 1) 2649 2650 # Test resting overtemp 2651 logger.write_result_to_html_report("Resting at 59°C") 2652 _plateset.thermistor1 = 59 2653 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 2) 2654 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 2) 2655 logger.write_result_to_html_report("Resting at 15°C") 2656 _plateset.thermistor1 = 15 2657 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 3) 2658 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 3) 2659 2660 # Test discharging overtemp 2661 logger.write_result_to_html_report("Discharging at -100 mA, 59°C") 2662 with _bms.load(0.100): 2663 _plateset.thermistor1 = 59 2664 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 4) 2665 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 4) 2666 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2667 _plateset.thermistor1 = 15 2668 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 5) 2669 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 5) 2670 2671 # Test charging overtemp 2672 logger.write_result_to_html_report("Charging at 100 mA, 54°C") 2673 with _bms.charger(16.8, 0.200): 2674 _plateset.thermistor1 = 54 2675 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 2676 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 2677 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2678 _plateset.thermistor1 = 15 2679 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 2680 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3) 2681 2682 # Test charging permanent disable 2683 logger.write_result_to_html_report("Charging at 100 mA, 94°C") 2684 _plateset.disengage_safety_protocols = True 2685 _plateset.thermistor1 = 94 2686 _plateset.disengage_safety_protocols = False 2687 with _bms.charger(16.8, 0.200): 2688 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2689 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2690 _plateset.thermistor1 = 15 2691 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2692 2693 # Test discharging permanent disable 2694 logger.write_result_to_html_report("Discharging at -100 mA, 94°C") 2695 _plateset.disengage_safety_protocols = True 2696 _plateset.thermistor1 = 94 2697 _plateset.disengage_safety_protocols = False 2698 with _bms.load(0.100): 2699 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2700 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2701 _plateset.thermistor1 = 15 2702 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2703 2704 # Test resting permanent disable 2705 _plateset.disengage_safety_protocols = True 2706 logger.write_result_to_html_report("Resting at 94°C") 2707 _plateset.thermistor1 = 94 2708 _plateset.disengage_safety_protocols = False 2709 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2710 logger.write_result_to_html_report("Resting at 15°C") 2711 _plateset.thermistor1 = 15 2712 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2713 2714 def test_undertemp_faults(self, serial_watcher: SerialWatcher): 2715 """ 2716 | Description | Test under temperature Fault | 2717 | :------------------- | :--------------------------------------------------------------------- | 2718 | GitHub Issue | turnaroundfactor/HITL#476 | 2719 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2720jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2721 | MIL-PRF Sections | 4.7.2.7 (Low Temperature Discharge) | 2722 | Instructions | 1. Set Default state at 15 </br>\ 2723 2. Resting at -35°C (at or below -33.2°C) </br>\ 2724 3. Discharging at -100 mA, -35°C (at or below -33.2°C) </br>\ 2725 4. Discharging at -100 mA, -30°C (at or above -32.2°C) </br>\ 2726 5. Charging at 100 mA, -25°C (at or below -23.2°C) </br>\ 2727 6. Charging at 100 mA, -20°C (at or above -22.2°C) | 2728 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2729 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2730 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2731 ⦁ Fault overtemp charge value is False (default state) </br>\ 2732 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2733 ⦁ Measure output fets disabled is False (default state) </br>\ 2734 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2735 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2736 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2737 ⦁ After resting permanent disable, permanent disable overtemp is True | 2738 | Estimated Duration | 10 seconds | 2739 | Note | This occurs when we read more than 20 mamps from the battery, if any \ 2740 of the cells are under 0 degrees Celsius this will trigger a fault. \ 2741 This will be cleared if we go above -2C. Regardless of current being \ 2742 measured, if we ever read below -20C, this should trigger a fault. \ 2743 This fault should not be cleared until we are above -18C | 2744 """ 2745 logger.write_info_to_report("Testing Undertemp") 2746 assert _bms.load and _bms.charger # Confirm hardware is available 2747 2748 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2749 _plateset.thermistor1 = 15 2750 2751 # Discharging flags 2752 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 2753 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 2754 2755 # Charging flags 2756 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 2757 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 2758 2759 logger.write_result_to_html_report("Resting at -35°C") 2760 _plateset.thermistor1 = -35 2761 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 2762 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 2763 logger.write_result_to_html_report("Resting at -30°C") 2764 _plateset.thermistor1 = -30 2765 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 2766 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 2767 2768 logger.write_result_to_html_report("Discharging at -100 mA, -35°C") 2769 with _bms.load(0.100): 2770 _plateset.thermistor1 = -35 2771 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 4) 2772 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 4) 2773 logger.write_result_to_html_report("Discharging at -100 mA, -30°C") 2774 _plateset.thermistor1 = -30 2775 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 5) 2776 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 5) 2777 2778 logger.write_result_to_html_report("Charging at 100 mA, -25°C") 2779 with _bms.charger(16.8, 0.200): 2780 _plateset.thermistor1 = -25 2781 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 2782 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 2783 logger.write_result_to_html_report("Charging at 100 mA, 20°C") 2784 _plateset.thermistor1 = 20 2785 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 2786 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3) 2787 2788 def set_exact_volts(self, cell: Cell, voltage: float, compensation: float = 0.08): 2789 """What the BMS reads won't exactly match the set voltage, thus we need slight adjustments.""" 2790 cell.exact_volts = voltage + compensation 2791 logger.write_debug_to_report(f"Cell is {cell.volts}V") 2792 2793 def test_overvoltage_faults(self, serial_watcher: SerialWatcher): 2794 """ 2795 | Description | Test over voltage faults | 2796 | :------------------- | :--------------------------------------------------------------------- | 2797 | GitHub Issue | turnaroundfactor/HITL#476 | 2798 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2799jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2800 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2801 | Instructions | 1. Rest at 0 mA, 3.8002 V </br>\ 2802 2. Charge at 100 mA, 4.21 V </br>\ 2803 3. Charge at 100 mA, 4.10 V </br>\ 2804 4. Charge at 100 mA, 4.26 V </br>\ 2805 5. Charge at 100 mA, 4.10 V | 2806 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 3.8002 V </br>\ 2807 ⦁ Fault over voltage charge is False at 3.8002 V </br>\ 2808 ⦁ Permanent disable over votlage charge is False at 3.8002 V </br>\ 2809 ⦁ Prefault over voltage charge is True at 4.21 V </br>\ 2810 ⦁ Fault over voltage charge is True at 4.21 V </br>\ 2811 ⦁ Prefault over voltage charge is False at 4.10 V </br>\ 2812 ⦁ Fault over voltage charge is False at 4.10 V </br>\ 2813 ⦁ Permanent disable over voltage is True at 4.26 V </br>\ 2814 ⦁ Permanent disable over voltage is False at 4.10 V | 2815 | Estimated Duration | 10 seconds | 2816 | Note | While charging, we need to monitor the voltage of our cells. \ 2817 Specifically, if a cell ever goes above 4.205 Volts, we should \ 2818 trigger a prefault. If this prefault exsists for more than 3 seconds, \ 2819 we then should trigger a full fault. If a cell ever gets to be above \ 2820 4.250 volts, we should trigger a permanent fault. If we go under 4.201 \ 2821 we should be able to clear the fault | 2822 """ 2823 logger.write_info_to_report("Testing Overvoltage") 2824 assert _bms.load and _bms.charger # Confirm hardware is available 2825 test_cell = _bms.cells[1] 2826 for cell in _bms.cells.values(): 2827 cell.disengage_safety_protocols = True 2828 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2829 self.set_exact_volts(cell, 4.0, 0) 2830 2831 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V") 2832 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 2833 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 2834 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 2835 2836 with _bms.charger(16.8, 0.200): 2837 logger.write_result_to_html_report("Charging at 100 mA, 4.205+ V") 2838 self.set_exact_volts(test_cell, 4.22, 0) 2839 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 2840 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 2841 2842 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2843 self.set_exact_volts(test_cell, 4.10, 0) 2844 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 2845 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 2846 2847 logger.write_result_to_html_report("Charging at 100 mA, 4.25+ V") 2848 self.set_exact_volts(test_cell, 4.27, 0) 2849 serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2) 2850 2851 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2852 self.set_exact_volts(test_cell, 4.10, 0) 2853 serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3) 2854 2855 def test_undervoltage_faults(self, serial_watcher: SerialWatcher): 2856 """ 2857 | Description | Test under voltage faults | 2858 | :------------------- | :--------------------------------------------------------------------- | 2859 | GitHub Issue | turnaroundfactor/HITL#476 | 2860 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2861jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2862 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2863 | Instructions | 1. Rest at 0 mA, 3.0 V </br>\ 2864 2. Rest at 0 mA, 2.3 V </br>\ 2865 3. Rest at 0 mA, 2.6 V </br>\ 2866 4. Charge at 500 mA, 2.3 V </br>\ 2867 5. Charge at 500 mA, 2.4 V </br>\ 2868 6. Charge at 500 mA, 2.2 V </br>\ 2869 5. Charge at 500 mA, 2.6 V | 2870 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0mA, 3.0 V </br>\ 2871 ⦁ Fault slumber under voltage discharge is False at 0mA, 3.0 V </br>\ 2872 ⦁ Prefault under voltage charge is False at 0mA, 3.0 V </br>\ 2873 ⦁ Permanent disable under voltage is False at 0mA, 3.0 V </br>\ 2874 ⦁ Prefault under voltage discharge is True when resting at 0mA, 2.3 V </br>\ 2875 ⦁ Fault slumber under voltage is True when resting at 0mA, 2.3 V </br>\ 2876 ⦁ Prefault under voltage discharge is False when resting at 0mA, 2.6 V </br>\ 2877 ⦁ Fault slumber under voltage is False when resting at 0mA, 2.6 V </br>\ 2878 ⦁ Prefault under voltage charge is True when charging at 500mA, 2.3 V </br>\ 2879 ⦁ Fault slumber under voltage is True when charging at 500mA, 2.3 V </br>\ 2880 ⦁ Prefault under voltage discharge is False when charging at 500mA, 2.4 V </br>\ 2881 ⦁ Fault slumber under voltage is False when charging at 500mA, 2.4 V </br>\ 2882 ⦁ Permanent disable under voltage is True when charging at 500mA, 2.2 V </br>\ 2883 ⦁ Permanent disable under voltage is False when charging at 500mA, 2.6 V | 2884 | Estimated Duration | 10 seconds | 2885 | Note | This has also been validated in software, meaning the logic should \ 2886 properly handle a situation with a cell discharging too low, however \ 2887 this has not yet been tested in hardware with a cell sensor reading \ 2888 that low of voltage and triggering a fault. If we are reading less \ 2889 than 20mamps from the cells, we should be able to trigger an \ 2890 under-voltage fault. If we read less than 2.4 volts, we must \ 2891 trigger a fault. If this fault persists for over 1 second, we \ 2892 should then trigger a full fault. We will not clear this fault \ 2893 unless we are able to read above 2.5 volts. If we are reading over \ 2894 20 mamps and a cell reads less than 2.325 volts, we must trigger a \ 2895 cell voltage charge min prefault, if this persists for another bms \ 2896 software cycle we will trigger a full fault. This fault will clear \ 2897 when we read above this voltage. If the cell voltage ever goes under \ 2898 2.3 while charging, we must trigger a permanent fault. | 2899 """ 2900 logger.write_info_to_report("Testing Undervoltage") 2901 assert _bms.load and _bms.charger # Confirm hardware is available 2902 test_cell = _bms.cells[1] 2903 for cell in _bms.cells.values(): 2904 cell.disengage_safety_protocols = True 2905 # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2906 self.set_exact_volts(cell, 2.6, 0.00) 2907 2908 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V") 2909 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 2910 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 2911 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 2912 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 2913 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 2914 2915 with _bms.load(0.500): 2916 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V") 2917 self.set_exact_volts(test_cell, 2.320, 0.00) 2918 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2, wait_time=600) 2919 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2, wait_time=600) 2920 2921 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V") 2922 self.set_exact_volts(test_cell, 2.6, 0.00) 2923 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3, wait_time=600) 2924 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3, wait_time=600) 2925 2926 with _bms.charger(16.8, 0.500): 2927 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V") 2928 self.set_exact_volts(test_cell, 2.320, 0.00) 2929 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2, wait_time=600) 2930 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2, wait_time=600) 2931 2932 logger.write_result_to_html_report("Charging at 500 mA, 2.4 V") 2933 self.set_exact_volts(test_cell, 2.6, 0.00) 2934 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 2935 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 2936 2937 logger.write_result_to_html_report("Charging at 500 mA, 2.2 V") 2938 self.set_exact_volts(test_cell, 2.2, 0.00) 2939 serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2) 2940 2941 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V") 2942 self.set_exact_volts(test_cell, 2.6, 0.00) 2943 serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3) 2944 2945 def test_cell_imbalance(self, serial_watcher: SerialWatcher): 2946 """ 2947 | Description | Test Cell Imbalance | 2948 | :------------------- | :--------------------------------------------------------------------- | 2949 | GitHub Issue | turnaroundfactor/HITL#476 | 2950 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2951jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2952 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 2953 | Instructions | 1. Rest at 0 mA, 3.8 V </br>\ 2954 2. Rest at 0 mA, 2.0 V </br>\ 2955 3. Rest at 0 mA, 3.8 V | 2956 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2957 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2958 ⦁ Prefault cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2959 ⦁ Permanent disable cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2960 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V | 2961 | Estimated Duration | 10 seconds | 2962 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 2963 """ 2964 logger.write_info_to_report("Testing Cell Imbalance") 2965 assert _bms.load and _bms.charger # Confirm hardware is available 2966 test_cell = _bms.cells[1] 2967 test_cell.disengage_safety_protocols = True 2968 2969 # Set all cell voltages 2970 for cell in _bms.cells.values(): 2971 cell.volts = CELL_VOLTAGE 2972 2973 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2974 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2975 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 2976 2977 self.set_exact_volts(test_cell, 2.0) 2978 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2979 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2980 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True, 2) 2981 2982 self.set_exact_volts(test_cell, CELL_VOLTAGE) 2983 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2984 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2985 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", False, 3) 2986 2987 def test_overvoltage_overtemp_faults(self, serial_watcher: SerialWatcher): 2988 """ 2989 | Description | Test combined high temperature & high voltage | 2990 | :------------------- | :--------------------------------------------------------------------- | 2991 | GitHub Issue | turnaroundfactor/HITL#476 | 2992 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2993jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2994 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2995 | Instructions | 1. Rest at 0 mA, 3.8002 V, 15°C </br>\ 2996 2. Charge at 100 mA, 4.21 V, 54°C </br>\ 2997 3. Charge at 100 mA, 4.10 V, 15°C </br>\ 2998 4. Charge at 100 mA, 4.26 V, 94°C </br>\ 2999 5. Charge at 100 mA, 4.10 V, 15°C | 3000 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3001 ⦁ Fault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3002 ⦁ Permanent disable over votlage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3003 ⦁ Prefault over voltage charge is True at 4.21 V, 54°C </br>\ 3004 ⦁ Fault over voltage charge is True at 4.21 V, 54°C </br>\ 3005 ⦁ Prefault over temperature charge is True at 4.21 V, 54°C </br>\ 3006 ⦁ Fault over temperature charge is True at 4.21 V, 54°C </br>\ 3007 ⦁ Prefault over voltage charge is False at 4.10 V, 15°C </br>\ 3008 ⦁ Prefault over temperature charge is False at 4.10 V, 15°C </br>\ 3009 ⦁ Fault over voltage charge is False at 4.10 V, 15°C </br>\ 3010 ⦁ Fault over temperature charge is False at 4.10 V </br>\ 3011 ⦁ Permanent disable over voltage is True at 4.26 V, 94°C </br>\ 3012 ⦁ Permanent disable over temperature is True at 4.26 V, 94°C </br>\ 3013 ⦁ Permanent disable over voltage is False at 4.10 V, 15°C </br>\ 3014 ⦁ Permanent disable over temperature is False at 4.10 V, 15°C | 3015 | Estimated Duration | 10 seconds | 3016 | Note | Combine over voltage and over temperature faults | 3017 """ 3018 logger.write_info_to_report("Testing Overvoltage with Overtemp") 3019 assert _bms.load and _bms.charger # Confirm hardware is available 3020 test_cell = _bms.cells[1] 3021 for cell in _bms.cells.values(): 3022 cell.disengage_safety_protocols = True 3023 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3024 self.set_exact_volts(cell, 4.0, 0.0) 3025 3026 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 3027 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 3028 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 3029 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 3030 3031 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 3032 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 3033 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 3034 3035 with _bms.charger(16.8, 0.200): 3036 logger.write_result_to_html_report("Charging at 100 mA, 4.21 V, 54°C") 3037 self.set_exact_volts(test_cell, 4.22, 0.0) 3038 _plateset.thermistor1 = 54 3039 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 3040 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 3041 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 3042 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 3043 3044 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V, 15°C") 3045 self.set_exact_volts(test_cell, 4.10, 0.0) 3046 _plateset.thermistor1 = 15 3047 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 3048 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 3049 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 3050 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3) 3051 3052 def test_undervoltage_undertemp_faults(self, serial_watcher: SerialWatcher): 3053 """ 3054 | Description | Test combined low temperature & low voltage | 3055 | :------------------- | :--------------------------------------------------------------------- | 3056 | GitHub Issue | turnaroundfactor/HITL#476 | 3057 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3058jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3059 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature Discharge) | 3060 | Instructions | 1. Rest at 0 mA, 3.0 V, 15°C </br>\ 3061 2. Rest at 0 mA, 2.3 V, -21°C </br>\ 3062 3. Rest at 0 mA, 2.6 V, -17°C </br>\ 3063 4. Charge at 500 mA, 2.3 V, -25°C </br>\ 3064 5. Charge at 500 mA, 2.4 V, -20°C </br>\ 3065 6. Charge at 500 mA, 2.2 V, 15°C </br>\ 3066 7. Charge at 500 mA, 2.6 V, 15°C | 3067 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3068 ⦁ Fault slumber under voltage charge is False at 0 mA, 3.0 V, 15°C</br>\ 3069 ⦁ Prefault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3070 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3071 ⦁ Permanent disable under voltage is False at 0 mA, 3.0 V, 15°C </br>\ 3072 ⦁ Prefault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3073 ⦁ Fault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3074 ⦁ Prefault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3075 ⦁ Fault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3076 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3077 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3078 ⦁ Prefault under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3079 ⦁ Fault slumber under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3080 ⦁ Prefault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3081 ⦁ Fault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3082 ⦁ Prefault under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3083 ⦁ Fault slumber under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3084 ⦁ Prefault under temperature discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3085 ⦁ Fault under temperature discharge is False at 0 mA, 2.6 V, -17°C</br>\ 3086 ⦁ Prefault under voltage charge is True at 500 mA, 2.3 V, -25°C </br>\ 3087 ⦁ Fault slumber under voltage charge is True at 500 mA, 2.3 V, -25°C</br>\ 3088 ⦁ Prefault under temperature charge is True at 500 mA, 2.3 V, -25°C</br>\ 3089 ⦁ Fault under temperature charge is True at 500 mA, 2.3 V, -25°C </br>\ 3090 ⦁ Prefault under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3091 ⦁ Fault slumber under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3092 ⦁ Prefault under temperature charge is False at 500 mA, 2.4 V, -20°C</br>\ 3093 ⦁ Fault under temperature charge is False at 500 mA, 2.4 V, -20°C </br>\ 3094 ⦁ Permanent disable under voltage is True at 500 mA, 2.2 V, 15°C </br>\ 3095 ⦁ Permanent disable under voltage is False at 500 mA, 2.6 V, 15°C | 3096 | Estimated Duration | 10 seconds | 3097 | Note | Combine under voltage and under temperature faults | 3098 """ 3099 3100 logger.write_info_to_report("Testing Undervoltage with Undertemp") 3101 assert _bms.load and _bms.charger # Confirm hardware is available 3102 test_cell = _bms.cells[1] 3103 for cell in _bms.cells.values(): 3104 cell.disengage_safety_protocols = True 3105 self.set_exact_volts( 3106 cell, 2.8, 0 3107 ) # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3108 3109 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V, 15°C") 3110 _plateset.thermistor1 = 15 3111 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 3112 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 3113 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 3114 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 3115 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 3116 3117 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 3118 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 3119 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 3120 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 3121 3122 with _bms.load(0.500): 3123 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V, -35°C") 3124 self.set_exact_volts(test_cell, 2.320, 0) 3125 _plateset.thermistor1 = -35 3126 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2) 3127 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2) 3128 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 3129 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 3130 3131 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V, -30°C") 3132 self.set_exact_volts(test_cell, 2.8, 0) 3133 _plateset.thermistor1 = -30 3134 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3) 3135 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3) 3136 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 3137 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 3138 3139 with _bms.charger(16.8, 0.500): 3140 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V, -25°C") 3141 self.set_exact_volts(test_cell, 2.320, 0.00) 3142 _plateset.thermistor1 = -25 3143 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2) 3144 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2) 3145 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 3146 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 3147 3148 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V, -20°C") 3149 self.set_exact_volts(test_cell, 2.8, 0) 3150 _plateset.thermistor1 = -20 3151 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 3152 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 3153 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 3154 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3) 3155 3156 def test_cell_imbalance_charge(self, serial_watcher: SerialWatcher): 3157 """ 3158 | Description | Test Cell Imbalance | 3159 | :------------------- | :--------------------------------------------------------------------- | 3160 | GitHub Issue | turnaroundfactor/HITL#476 | 3161 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3162jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3163 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 3164 | Instructions | 1. Rest at 0 mA </br>\ 3165 2. Charge at 1 mA </br>\ 3166 3. Rest at 0 mA | 3167 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA </br>\ 3168 ⦁ Permanent disable cell imbalance is False after resting at 0mA </br>\ 3169 ⦁ Prefault cell imbalance is True after resting at 1 A </br>\ 3170 ⦁ Permanent disable cell imbalance is True after resting 1 A </br>\ 3171 ⦁ Prefault cell imbalance is True after resting at 0 A </br>\ 3172 ⦁ Permanent disable cell imbalance is False after resting at 0 A | 3173 | Estimated Duration | 10 seconds | 3174 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 3175 """ 3176 logger.write_info_to_report("Testing Cell Imbalance Charge") 3177 assert _bms.load and _bms.charger # Confirm hardware is available 3178 3179 test_cell = _bms.cells[1] 3180 test_cell.disengage_safety_protocols = True 3181 for cell in _bms.cells.values(): 3182 cell.exact_volts = 4.2 3183 3184 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3185 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3186 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 3187 3188 with _bms.charger(16.8, 1): 3189 self.set_exact_volts(test_cell, 2.5) 3190 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3191 logger.write_result_to_html_report(f"Charging at 1 A, {', '.join(voltages)}") 3192 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True) 3193 3194 test_cell.exact_volts = 4.2 3195 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3196 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3197 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True) 3198 3199 def test_current_limit(self, serial_watcher: SerialWatcher): 3200 """ 3201 | Description | Confirm A fault is raised after 3.5A | 3202 | :------------------- | :----------------------------------------------------------------------------------- | 3203 | GitHub Issue | turnaroundfactor/HITL#476 | 3204 | Instructions | 1. Rest for 30 second </br>\ 3205 2. Charge at 3.75 amps (the max limit will be 3.5 amps) </br>\ 3206 3. Verify a prefault and fault are raised. </br>\ 3207 4. Rest until faults clear </br>\ 3208 5. Charge at 3.75 amps </br>\ 3209 6. Verify a prefault occurred but not a fault | 3210 | Pass / Fail Criteria | Pass if faults are raised | 3211 | Estimated Duration | 2 minutes | 3212 | Notes | We use 3.5A as a limit due to hardware limitations | 3213 """ 3214 3215 logger.write_info_to_report("Testing Current Limit") 3216 assert _bms.load and _bms.charger # Confirm hardware is available 3217 3218 # Rest and make sure no faults are active 3219 logger.write_result_to_html_report("Confirm no faults are active") 3220 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 1) 3221 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 1) 3222 3223 # Charge at 3.25 amps and verify faults are raised 3224 logger.write_result_to_html_report("Charging 3.75 A") 3225 with _bms.charger(16.8, 3.75): 3226 serial_watcher.assert_true("flags.prefault_overcurrent_charge", True, 2, wait_time=60) 3227 serial_watcher.assert_true("flags.fault_overcurrent_charge", True, 2, wait_time=60) 3228 3229 # Rest until faults clear 3230 logger.write_result_to_html_report("Resting") 3231 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 3) 3232 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 3) 3233 assert ( 3234 serial_watcher.events["flags.fault_overcurrent_charge"][2].bms_time 3235 - serial_watcher.events["flags.fault_overcurrent_charge"][1].bms_time 3236 ).total_seconds() >= 60 3237 3238 def test_undertemp_charge_rate(self, serial_watcher: SerialWatcher): 3239 """ 3240 | Description | Confirm a fault is raised at or above 3.1A when below 5°C | 3241 | :------------------- | :----------------------------------------------------------------------------------- | 3242 | GitHub Issue | turnaroundfactor/HITL#611 | 3243 | Instructions | 1. Rest for 30 second </br>\ 3244 2. Charge at 3.3 amps below 5°C </br>\ 3245 3. Verify a prefault (before 1 second) and fault (after 1 second) are raised. </br>\ 3246 4. Charge above 7°C until faults clear | 3247 | Pass / Fail Criteria | Pass if faults are raised | 3248 | Estimated Duration | 2 minutes | 3249 """ 3250 3251 logger.write_info_to_report("Testing Undertemp charge rate") 3252 assert _bms.load and _bms.charger # Confirm hardware is available 3253 3254 # Rest and make sure no faults are active 3255 logger.write_result_to_html_report("Confirm no faults are active") 3256 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 1) 3257 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 1) 3258 3259 # Discharge at 3.1+ amps and verify faults are raised 3260 logger.write_result_to_html_report("Discharging at 3.1A+") 3261 with _bms.charger(16.8, 3.3): 3262 _plateset.thermistor1 = 3 3263 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", True, 2, wait_time=60) 3264 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", True, 2, wait_time=60) 3265 _plateset.thermistor1 = 8 3266 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 3, wait_time=90) 3267 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 3, wait_time=90) 3268 3269 def test_overcurrent_discharge_sustained(self, serial_watcher: SerialWatcher): 3270 """ 3271 | Description | Test Over Current Discharge Sustained Fault | 3272 | :------------------- | :--------------------------------------------------------------------- | 3273 | GitHub Issue | turnaroundfactor/HITL#762 | 3274 | Instructions | 1. Check prefault_sw_overcurrent_discharge and fault_sw_overcurrent_discharge </br>\ 3275 2. Set current to less than -2.5 amps </br>\ 3276 3. Confirm faults listed in #1 are true </br>\ 3277 4. Set current to 0 amps (rest) </br>\ 3278 5. Confirm faults listed in #1 are false | 3279 | Pass / Fail Criteria | ⦁ Prefault overCurrent Discharge Sustained is False at start </br>\ 3280 ⦁ Fault overCurrent Discharge Sustained is False at start </br>\ 3281 ⦁ Prefault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3282 ⦁ Fault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3283 ⦁ Prefault overCurrent Discharge Sustained is False when current is 0 A </br>\ 3284 ⦁ Fault overCurrent Discharge Sustained is False when current is 0 A | 3285 | Estimated Duration | 10 seconds | 3286 """ 3287 3288 logger.write_info_to_report("Testing Overcurrent Discharge Sustained Faults") 3289 assert _bms.load and _bms.charger # Confirm hardware is available 3290 3291 logger.write_info_to_report("Sw overcurrent protection high current short time") 3292 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 1) 3293 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 1) 3294 3295 logger.write_result_to_html_report("Setting current to less than -2.6 amps") 3296 with _bms.load(2.6): 3297 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", True, 2) 3298 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", True, 2) 3299 3300 logger.write_result_to_html_report("Resting") 3301 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3302 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3303 3304 logger.write_info_to_report("Sw overcurrent protection low current longer time") 3305 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 1) 3306 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 1) 3307 3308 logger.write_result_to_html_report("Setting current to less than -2.4 amps") 3309 with _bms.load(2.4): 3310 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", True, 2) 3311 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", True, 2) 3312 3313 logger.write_result_to_html_report("Resting") 3314 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10) 3315 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10) 3316 3317 3318@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 3319class TestStateOfCharge: 3320 """Confirm cell sim SOC and BMS SOC are within 5%""" 3321 3322 def test_state_of_charge(self): 3323 """ 3324 | Description | Confirm cell sim state of charge and BMS state of charge are within 5% | 3325 | :------------------- | :--------------------------------------------------------------------- | 3326 | GitHub Issue | turnaroundfactor/HITL#474 | 3327 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3328jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D19) | 3329 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 3330 | Instructions | 1. Set cell sims SOC to 5% </br>\ 3331 2. Confirm serial SOC is within 5% </br>\ 3332 3. Increment the cell sim SOC by 1% and repeat steps 1 & 2. | 3333 | Pass / Fail Criteria | ⦁ Serial state of charge is within 5% | 3334 | Estimated Duration | 20 seconds | 3335 | Note | When tested as specified in MIL-PERF section 4.7.2.15.1, SMBus data \ 3336 output shall be accurate within +0/-5% of the actual state of charge \ 3337 for the battery under test throughout the discharge. Manufacturer and \ 3338 battery data shall be correctly programmed (see 4.7.2.15.1). | 3339 """ 3340 3341 percent_failed = [] 3342 allowed_error = 0.05 3343 max_error = 0.0 3344 failure_rate = [] 3345 3346 for percent in range(5, 101): 3347 logger.write_info_to_report(f"Setting cell sims SOC to {percent}%") 3348 3349 for cell in _bms.cells.values(): 3350 cell.state_of_charge = percent / 100 3351 3352 time.sleep(2) 3353 serial_monitor.read() # Clear the latest serial buffer 3354 3355 serial_data = serial_monitor.read() 3356 percent_charged = serial_data["percent_charged"] 3357 3358 max_error = max(max_error, abs(percent_charged - percent)) 3359 if not (percent - 5) <= percent_charged <= (percent + 5): 3360 failure_rate.append(1) 3361 logger.write_warning_to_report( 3362 f"State of Charge is not within {allowed_error:.0%} of {percent}, received {percent_charged}%" 3363 ) 3364 percent_failed.append(str(percent)) 3365 else: 3366 failure_rate.append(0) 3367 logger.write_info_to_report( 3368 f"State of Charge is within {allowed_error:.0%} of {percent}% after changing cell sims SOC" 3369 ) 3370 3371 if len(percent_failed) > 0: 3372 message = ( 3373 f"SOC Error: {max_error / 100:.1%} ≰ {allowed_error:.0%} " 3374 f"[{sum(failure_rate) / len(failure_rate):.1%} failed]" 3375 ) 3376 logger.write_result_to_html_report(message) 3377 pytest.fail(message) 3378 else: 3379 logger.write_result_to_html_report(f"SOC Error: {max_error:.1%} ≤ {allowed_error:.0%}") 3380 3381 3382@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3383class TestTemperatureAccuracy: 3384 """Compare BMS and HITL temps.""" 3385 3386 class TemperatureDiscrepancyTherm1(CSVRecordEvent): 3387 """@private Compare HITL temperature to reported temperature.""" 3388 3389 allowable_error = 5.0 3390 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 3391 3392 @classmethod 3393 def failed(cls) -> bool: 3394 """Check if test parameters were exceeded.""" 3395 return bool(cls.max.error > cls.allowable_error) 3396 3397 @classmethod 3398 def verify(cls, _row, serial_data, _cell_data): 3399 """Temperature within range""" 3400 row_data = SimpleNamespace(hitl_c=_plateset.thermistor1, bms_c=serial_data["dk_temp"] / 10 - 273) 3401 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 3402 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3403 3404 @classmethod 3405 def result(cls): 3406 """Detailed test result information.""" 3407 return ( 3408 f"Thermistor 1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 3409 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 3410 ) 3411 3412 class TemperatureDiscrepancyTherm2(CSVRecordEvent): 3413 """@private Compare HITL temperature to reported temperature.""" 3414 3415 allowable_error = 5.0 3416 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 3417 3418 @classmethod 3419 def failed(cls) -> bool: 3420 """Check if test parameters were exceeded.""" 3421 return bool(cls.max.error > cls.allowable_error) 3422 3423 @classmethod 3424 def verify(cls, _row, serial_data, _cell_data): 3425 """Temperature within range""" 3426 row_data = SimpleNamespace(hitl_c=_plateset.thermistor2, bms_c=serial_data["dk_temp1"] / 10 - 273) 3427 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 3428 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3429 3430 @classmethod 3431 def result(cls): 3432 """Detailed test result information.""" 3433 return ( 3434 f"Thermistor 2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 3435 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 3436 ) 3437 3438 def test_temperature_accuracy(self): 3439 """ 3440 | Description | TAF: Ensure that temperature measurements are accurate | 3441 | :------------------- | :--------------------------------------------------------------------------- | 3442 | GitHub Issue | turnaroundfactor/HITL#401 | 3443 | MIL-PRF Section | 3.5.8.3 (Accuracy) </br>\ 3444 4.7.2.14.3 (Accuracy During Discharge) | 3445 | Instructions | 1. Set THERM1 and THERM2 to -40C </br>\ 3446 2. Set cell voltages to 3.7V per cell </br>\ 3447 3. Increment THERM1 and THERM2 in 5C increments up to and including 60C </br>\ 3448 4. Record the following data at each current increment </br>\ 3449 HITL: THERM1 Measurement, THERM2 Measurement </br>\ 3450 SERIAL: THERM1, THERM2" | 3451 | Pass / Fail Criteria | HITL and Serial are within 5% | 3452 | Estimated Duration | 1 minute | 3453 """ 3454 _bms.timer.reset() # Keep track of runtime 3455 _plateset.disengage_safety_protocols = True 3456 3457 for target_c in range(-40, 65, 5): 3458 _plateset.thermistor1 = _plateset.thermistor2 = target_c 3459 time.sleep(1) 3460 logger.write_info_to_report( 3461 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, " 3462 f"Temp1: {_plateset.thermistor1}, Temp2: {_plateset.thermistor2}" 3463 ) 3464 _bms.csv.cycle.record(_bms.timer.elapsed_time) 3465 time.sleep(0.5) 3466 3467 # Check results 3468 if CSVRecordEvent.failed(): 3469 pytest.fail(CSVRecordEvent.result()) 3470 3471 3472@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3473class TestCurrentAccuracy: 3474 """Run a test for current accuracy""" 3475 3476 class CurrentCellAccuracy(CSVRecordEvent): 3477 """@private Compare terminal current to reported current.""" 3478 3479 allowable_error = 0.01 3480 max = SimpleNamespace(hitl_a=0, bms_a=0, error=0.0) 3481 3482 @classmethod 3483 def failed(cls) -> bool: 3484 """Check if test parameters were exceeded.""" 3485 return bool(cls.max.error > cls.allowable_error) 3486 3487 @classmethod 3488 def verify(cls, row, serial_data, _cell_data): 3489 """Current within range""" 3490 if not _plateset.load_switch: 3491 row_data = SimpleNamespace(hitl_a=row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 3492 else: 3493 row_data = SimpleNamespace(hitl_a=-row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 3494 row_data.error = abs((row_data.bms_a - row_data.hitl_a) / row_data.hitl_a) 3495 if abs(row_data.hitl_a) > 0.100: # Ignore currents within 100mA to -100mA 3496 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3497 3498 @classmethod 3499 def result(cls): 3500 """Detailed test result information.""" 3501 return ( 3502 f"Current error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 3503 f"(HITL: {cls.max.hitl_a * 1000:.3f} mA, BMS: {cls.max.bms_a * 1000:.3f} mA)" 3504 ) 3505 3506 def test_current_accuracy(self): 3507 """ 3508 | Description | Test the cell current accuracy | 3509 | :------------------- | :--------------------------------------------------------------------- | 3510 | GitHub Issue | turnaroundfactor/HITL#400 | 3511 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3512 4.7.2.14.3 (Accuracy During Discharge) | 3513 | Instructions | 1. Set thermistors to 23C | 3514 2. Set cell voltages to 3.7V per cell | 3515 3. Increment the charing current from 100mA to 3A in 50mA increments | 3516 4. Increment the discharging current from 100mA to 3A in 50 mA | 3517 increments | 3518 5. Record the following data at each current increment | 3519 HITL: Current (A) | 3520 SERIAL: Current (A) | 3521 | Pass / Fail Criteria | Pass IF: | 3522 SERIAL Current measurements agree with the HITL Terminal Current | 3523 measurements to within 1% for abs(Terminal Current >= 100mA) | 3524 - Result highest current (mA) discrepancy | 3525 | Estimated Duration | ?? | 3526 | Note | ?? | 3527 """ 3528 _bms.timer.reset() # Keep track of runtime 3529 3530 with _bms.charger(16.8, 0.1): 3531 for target_ma in range(100, 2050, 50): 3532 _bms.charger.amps = target_ma / 1000 3533 time.sleep(1) 3534 logger.write_info_to_report( 3535 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.charger.amps:.3f}" 3536 ) 3537 time.sleep(1) 3538 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.charger.amps) 3539 time.sleep(1) 3540 3541 with _bms.load(0.1): 3542 for target_ma in range(100, 2050, 50): 3543 time.sleep(1) 3544 _bms.load.amps = target_ma / 1000 3545 time.sleep(1) 3546 logger.write_info_to_report( 3547 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.load.amps:.3f}" 3548 ) 3549 time.sleep(1) 3550 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.load.amps) 3551 3552 if CSVRecordEvent.failed(): 3553 pytest.fail(CSVRecordEvent.result()) 3554 3555 3556def battery0_voltage_check(serial_data: dict[str, int | bool | str]): 3557 """Checks the SERIAL Battery 0 Voltage""" 3558 expected_charge = 14.8 3559 voltage_low_range = expected_charge - 0.100 3560 voltage_high_range = expected_charge + 2 3561 voltage_value = f"{expected_charge}V -100mV/+2V" 3562 3563 serial_voltage = float(serial_data["mvolt_battery"]) / 1000 3564 3565 if voltage_low_range <= serial_voltage <= voltage_high_range: 3566 logger.write_result_to_html_report( 3567 f"Battery 0 Voltage is {serial_voltage}V at the start of this test, " 3568 f"which is within within range of: {voltage_value}" 3569 ) 3570 3571 else: 3572 logger.write_failure_to_html_report( 3573 f"Battery 0 Voltage is {serial_voltage}V at the start of this test, " 3574 f"which is not within within range of: {voltage_value}" 3575 ) 3576 pytest.fail() 3577 3578 3579@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3580class TestCeaseCharging(CSVRecordEvent): 3581 """Run a test to cease charging after""" 3582 3583 def test_cease_charging(self): 3584 """ 3585 | Description | Cease charging if charger keeps trying too long | 3586 | :------------------- | :--------------------------------------------------------------------- | 3587 | GitHub Issue | turnaroundfactor/HITL#745 | 3588 #TODO: Update MIL-PRF sections 3589 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3590 4.7.2.14.3 (Accuracy During Discharge) | 3591 | Instructions | 1. Set thermistors to 23C </br>\ 3592 2. Put cells in a rested state at 3.7V per cell </br>\ 3593 3. Charge at 40 mA (do not let charger side terminate charge) </br>\ 3594 4. Wait 3,480 seconds (0:58 HR:MIN) </br>\ 3595 5. Wait 240 seconds (1:02 HR:MIN) </br>\ 3596 6. Disable CE </br>\ 3597 7. Wait 5 Seconds </br>\ 3598 8. Enable CE </br>\ 3599 9. Wait 5 seconds </br>\ 3600 10. Attempt to charge at 2A | 3601 | Pass / Fail Criteria | Pass IF at Step #... </br>\ 3602 1. SERIAL THERM1 and THERM2 are 23C +/- 1.1C </br>\ 3603 2. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3604 3. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3605 4. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3606 4. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3607 4. SERIAL No Fault Flags </br>\ 3608 5. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3609 5. HITL Charge Current is 0 mA +/- 1mA </br>\ 3610 5. SERIAL Flag OverTime_Charge is flagged </br>\ 3611 10. HITL Charge Current is 2A +/- 30mA | 3612 | Estimated Duration | 64 minutes | 3613 | Note | ?? | 3614 """ 3615 serial_data = serial_monitor.read() 3616 3617 set_temp = 23 3618 # Check THERM 1 & Therm 2 3619 therm_one = serial_data["dk_temp"] / 10 - 273 3620 therm_two = serial_data["dk_temp1"] / 10 - 273 3621 low_range = set_temp - 1.1 3622 high_range = set_temp + 1.1 3623 temp_range = f"{set_temp}°C +/- 1.1°C" 3624 3625 if low_range <= therm_one <= high_range: 3626 logger.write_result_to_html_report( 3627 f"THERM1 was {therm_one:.1f}°C, which was within the expected range of {temp_range}" 3628 ) 3629 else: 3630 logger.write_failure_to_html_report( 3631 f"THERM1 was {therm_one:.1f}°C, which was not within the expected range of {temp_range}" 3632 ) 3633 pytest.fail() 3634 3635 if low_range <= therm_two <= high_range: 3636 logger.write_result_to_html_report( 3637 f"THERM2 was {therm_two:.1f}°C, which was within the expected range of {temp_range}" 3638 ) 3639 else: 3640 logger.write_failure_to_html_report( 3641 f"THERM2 was {therm_two:.1f}°C, which was not within the expected range of {temp_range}" 3642 ) 3643 pytest.fail() 3644 3645 # Check Serial Battery 0 Voltage: 3646 battery0_voltage_check(serial_data) 3647 3648 # Charge at 40 mA -- keep voltage at 3.7 3649 with _bms.charger(16.8, 0.040): 3650 logger.write_info_to_report("Charging at 40mA") 3651 time.sleep(60) 3652 3653 high_current_range = 0.020 + 0.070 3654 low_current_range = 0.020 3655 3656 expected_current_range = "20mA +70mA/-0mA" 3657 terminal_current = _bms.charger.amps 3658 3659 if low_current_range <= terminal_current <= high_current_range: 3660 logger.write_result_to_html_report( 3661 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was within the expected " 3662 f"range of {expected_current_range}" 3663 ) 3664 else: 3665 logger.write_failure_to_html_report( 3666 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was not within the " 3667 f"expected range of {expected_current_range}" 3668 ) 3669 pytest.fail() 3670 3671 # 1 hour (debug mode) 3672 long_rest = 3480 3673 3674 logger.write_info_to_report(f"Waiting for {long_rest} seconds") 3675 time.sleep(long_rest) 3676 3677 serial_data = serial_monitor.read() 3678 3679 # Check Battery 0 Voltage 3680 battery0_voltage_check(serial_data) 3681 3682 # Check HITL Charge Current 3683 terminal_current = _bms.charger.amps 3684 3685 if low_current_range <= terminal_current <= high_current_range: 3686 logger.write_result_to_html_report( 3687 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3688 f"which was within the expected range of {expected_current_range}" 3689 ) 3690 else: 3691 logger.write_failure_to_html_report( 3692 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3693 f"which was not within the expected range of {expected_current_range}" 3694 ) 3695 pytest.fail() 3696 3697 # Check Serial OverTime_Charge 3698 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3699 if no_fault_flag is False: 3700 logger.write_result_to_html_report( 3701 f"OverTime Charge Fault was False, the expected value after waiting {long_rest} seconds" 3702 ) 3703 else: 3704 logger.write_failure_to_html_report( 3705 f"OverTime Charge Fault was True, which was not expected after waiting {long_rest} seconds" 3706 ) 3707 pytest.fail() 3708 3709 # Sleep for 240 seconds 3710 short_rest = 240 3711 logger.write_info_to_report(f"Waiting for {short_rest} seconds") 3712 time.sleep(short_rest) 3713 3714 serial_data = serial_monitor.read() 3715 3716 # Check Battery 0 Voltage 3717 battery0_voltage_check(serial_data) 3718 3719 # Check HITL Charge Current 3720 expected_charge = 0 3721 high_current_range = expected_charge + 0.020 3722 low_current_range = expected_charge - 0.020 3723 3724 expected_current_range = "0mA +/- 20mA" 3725 terminal_current = _bms.charger.amps 3726 3727 if low_current_range <= terminal_current <= high_current_range: 3728 logger.write_result_to_html_report( 3729 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3730 f"which was within the expected range of {expected_current_range}" 3731 ) 3732 else: 3733 logger.write_failure_to_html_report( 3734 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3735 f"which was not within the expected range of {expected_current_range}" 3736 ) 3737 pytest.fail() 3738 3739 # Check Overtime Charge Flag 3740 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3741 if no_fault_flag is True: 3742 logger.write_result_to_html_report( 3743 f"Overtime Charge Fault was True, the expected value after waiting {short_rest} seconds" 3744 ) 3745 else: 3746 logger.write_failure_to_html_report( 3747 f"Overtime Charge Fault was False, which was not expected after waiting {short_rest} seconds" 3748 ) 3749 pytest.fail() 3750 3751 # Wait 5 Seconds (Since BMS_Charger is disabled) 3752 time.sleep(5) 3753 3754 # Enable BMS Charger & Wait 5 Seconds before attempting to charge 3755 with _bms.charger(16.8, 2): 3756 time.sleep(5) 3757 3758 # Check HITL Charge Current 3759 terminal_current = _bms.charger.amps 3760 expected_current = 2 3761 expected_current_range = "2A +/- 30mA" 3762 high_current_range = expected_current + 0.03 3763 low_current_range = expected_current 3764 3765 if low_current_range <= terminal_current <= high_current_range: 3766 logger.write_result_to_html_report( 3767 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3768 f"which was within the expected range of {expected_current_range}" 3769 ) 3770 else: 3771 logger.write_failure_to_html_report( 3772 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3773 f"which was not within the expected range of {expected_current_range}" 3774 ) 3775 pytest.fail() 3776 3777 3778class TestColdTemperatureCharging(CSVRecordEvent): 3779 """Run a test for cold charging.""" 3780 3781 def test_cold_temperature_charging(self): 3782 """ 3783 | Description | Cold temperature charging | 3784 | :------------------- | :--------------------------------------------------------------------- | 3785 | GitHub Issue | turnaroundfactor/HITL#609 | 3786 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3787jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D42) | 3788 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3789 2. Put cells in rested state at 3.7V per cell </br>\ 3790 4. Attempt to charge at 3.2A </br>\ 3791 6. Set THERM1 and THERM2 to 0°C </br>\ 3792 7. Attempt to charge at 3.2A </br>\ 3793 7. Wait 5 seconds </br>\ 3794 7. Disable charging </br>\ 3795 7. Wait 65 seconds </br>\ 3796 7. Set THERM1 and THERM2 to 7°C </br>\ 3797 8. Attempt to charge at 3.2A | 3798 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3799 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA </br>\ 3800 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3801 ⦁ Expect HITL Terminal Current to be 0A +/- 30mA </br>\ 3802 ⦁ Expect Serial THERM1 & THERM 2 to be 7°C +/- 1.1°C </br>\ 3803 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA | 3804 | Estimated Duration | 17 seconds | 3805 """ 3806 3807 failed_tests = [] 3808 temperatures = [23, 0, 7] 3809 3810 for set_temp in temperatures: 3811 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 3812 3813 _plateset.disengage_safety_protocols = True 3814 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 3815 _plateset.disengage_safety_protocols = False 3816 3817 time.sleep(2) 3818 3819 # Get the serial data 3820 serial_data = serial_monitor.read() 3821 3822 # Convert temperature to Celsius from Kelvin 3823 therm_one = serial_data["dk_temp"] / 10 - 273 3824 therm_two = serial_data["dk_temp1"] / 10 - 273 3825 temp_range = f"{set_temp}°C +/- 1.1°C" 3826 low_range = set_temp - 1.1 3827 high_range = set_temp + 1.1 3828 3829 if low_range <= therm_one <= high_range: 3830 logger.write_result_to_html_report( 3831 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3832 ) 3833 else: 3834 logger.write_result_to_html_report( 3835 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3836 f"of {temp_range}</font>" 3837 ) 3838 failed_tests.append("THERM1") 3839 3840 if low_range <= therm_two <= high_range: 3841 logger.write_result_to_html_report( 3842 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3843 ) 3844 else: 3845 logger.write_result_to_html_report( 3846 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3847 f"expected range of {temp_range}</font>" 3848 ) 3849 failed_tests.append("THERM2") 3850 3851 logger.write_info_to_report("Attempting to charge at 1A") 3852 limit = 0.030 3853 charge_current = 3.2 3854 expected_current_range = f"3.2A +/- {limit}A" 3855 with _bms.charger(16.8, charge_current): 3856 time.sleep(1) 3857 if set_temp == 0: 3858 expected_current_range = f"0A +/- {limit}A" 3859 charge_current = 0 3860 charger_amps = _bms.charger.amps 3861 if charge_current - limit <= charger_amps <= charge_current + limit: 3862 logger.write_result_to_html_report( 3863 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 3864 f"expected range of {expected_current_range}" 3865 ) 3866 else: 3867 logger.write_result_to_html_report( 3868 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 3869 f"which was not within the expected range of {expected_current_range} </font>" 3870 ) 3871 failed_tests.append("HITL Terminal Current") 3872 3873 if set_temp == 0: 3874 time.sleep(5) 3875 if set_temp == 0: 3876 time.sleep(65) 3877 3878 if len(failed_tests) > 0: 3879 pytest.fail() 3880 3881 logger.write_result_to_html_report("All checks passed test") 3882 3883 3884class TestColdTemperatureCurrent(CSVRecordEvent): 3885 """Run a test for cold current.""" 3886 3887 def set_temperature(self, celsius: float) -> bool: 3888 """Set and check the temperature.""" 3889 test_failed = False 3890 3891 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {celsius}°C") 3892 3893 _plateset.disengage_safety_protocols = True 3894 _plateset.thermistor1 = _plateset.thermistor2 = celsius 3895 _plateset.disengage_safety_protocols = False 3896 3897 time.sleep(2) 3898 3899 # Get the serial data 3900 serial_data = serial_monitor.read() 3901 assert serial_data, "No serial data recieved." 3902 3903 # Convert temperature to Celsius from Kelvin 3904 therm_one = int(serial_data["dk_temp"]) / 10 - 273 3905 therm_two = int(serial_data["dk_temp1"]) / 10 - 273 3906 temp_range = f"{celsius}°C +/- 1.1°C" 3907 low_range = celsius - 1.1 3908 high_range = celsius + 1.1 3909 3910 if low_range <= therm_one <= high_range: 3911 logger.write_result_to_html_report( 3912 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3913 ) 3914 else: 3915 logger.write_result_to_html_report( 3916 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3917 f"of {temp_range}</font>" 3918 ) 3919 test_failed = True 3920 3921 if low_range <= therm_two <= high_range: 3922 logger.write_result_to_html_report( 3923 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3924 ) 3925 else: 3926 logger.write_result_to_html_report( 3927 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3928 f"expected range of {temp_range}</font>" 3929 ) 3930 test_failed = True 3931 return test_failed 3932 3933 def test_cold_temperature_current(self): 3934 """ 3935 | Description | Cold temperature current | 3936 | :------------------- | :--------------------------------------------------------------------- | 3937 | GitHub Issue | turnaroundfactor/HITL#609 | 3938 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3939jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D43) | 3940 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3941 2. Put cells in rested state at 3.7V per cell </br>\ 3942 3. Read SMBus charging current </br>\ 3943 4. Set THERM1 and THERM2 to 0°C </br>\ 3944 5. Read SMBus charging current | 3945 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3946 ⦁ Expect SMBus charging current 0x07D0 (2000 mA) </br>\ 3947 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3948 ⦁ Expect SMBus charging current 0x03EB (1000 mA) | 3949 | Estimated Duration | 17 seconds | 3950 | Note | 0x07D0 (Note: This is the default value for debug build, Production \ 3951 build should return 0x1770) | 3952 """ 3953 3954 for temperature, expected_value in ((23, 2000), (0, 1000)): 3955 test_failed = self.set_temperature(temperature) 3956 time.sleep(5) 3957 charging_current = _smbus.read_register(SMBusReg.CHARGING_CURRENT) 3958 if charging_current[0] == expected_value: 3959 logger.write_result_to_html_report(f"Charging current: {charging_current[0]} = {expected_value}") 3960 else: 3961 logger.write_failure_to_html_report(f"Charging current: {charging_current[0]} ≠ {expected_value}") 3962 test_failed = True 3963 3964 if test_failed: 3965 pytest.fail()
Shorten test times if True.
Cell capacity combined.
How long to wait for flash operations in seconds.
Default cell voltage.
53@pytest.fixture(scope="function", autouse=True) 54def reset_test_environment(request): 55 """ 56 Before each test, reset cell sims / BMS and set appropriate temperatures. 57 After each test, clean up modified objects. 58 59 Fixture arguments are provided in an abnormal way, see below tests for details on how to provide these arguments. 60 A default value is used if neither soc nor volts is provided. 61 62 :param float temperature: the initial temperature in C 63 :param float soc: the initial state of charge 64 :param float volts: the initial voltage 65 """ 66 global CELL_CAPACITY_AH 67 68 request.param = getattr(request, "param", {}) 69 70 # Reset cell sims 71 starting_temperature = request.param.get("temperature", 23) 72 if len(_bms.cells) > 0: 73 logger.write_info_to_report(f"Setting temperature to {starting_temperature}°C") 74 _plateset.thermistor1 = _plateset.thermistor2 = starting_temperature 75 76 logger.write_info_to_report("Powering down cell sims") 77 for cell in _bms.cells.values(): 78 cell.disengage_safety_protocols = True 79 cell.volts = 0.0001 80 time.sleep(5) 81 82 for cell in _bms.cells.values(): 83 new_soc = request.param.get("soc") or cell.volts_to_soc(request.param.get("volts")) or 0.50 84 logger.write_info_to_report(f"Powering up cell sim {cell.id} to {new_soc:%}") 85 cell.state_of_charge = new_soc 86 cell.disengage_safety_protocols = False 87 88 logger.write_info_to_report("Waiting 10 seconds for BMS...") 89 time.sleep(10) 90 91 if not CELL_CAPACITY_AH and (serial_data := serial_monitor.read()): # Get capacity from BMS 92 CELL_CAPACITY_AH = float(serial_data["milliamp_hour_capacity"]) / 1000 93 logger.write_info_to_report(f"Setting cell capacity to {CELL_CAPACITY_AH} Ah") 94 for cell in _bms.cells.values(): 95 cell.data.capacity = CELL_CAPACITY_AH 96 97 # Clear permanent disables 98 serial_monitor.read() # Clear latest serial buffer 99 serial_data = serial_monitor.read() 100 for key in serial_data: 101 if key.startswith(("flags.permanent", "flags.measure_output_fets_disabled")) and serial_data[key]: 102 # Erase flash 103 logger.write_warning_to_report("Detected permanent fault.") 104 logger.write_info_to_report("Erasing flash...") 105 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.ERASE_FLASH) 106 time.sleep(FLASH_SLEEP) # Wait for erase to complete 107 data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0] 108 logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}") 109 110 # Enable faults 111 logger.write_info_to_report("Enabling faults...") 112 try: 113 test_fault_enable() 114 except TimeoutError: 115 logger.write_error_to_report("Failed to enable faults.") 116 117 # Recalibrate 118 logger.write_info_to_report("Recalibrating...") 119 try: 120 TestCalibration().test_calibration() 121 except AssertionError: 122 logger.write_error_to_report("Failed to calibrate.") 123 break 124 125 CSVRecordEvent.current_test(request.cls) # Automatically register any tests defined in this class 126 127 yield # Run test 128 129 CSVRecordEvent.failed() # Record results regardless of failure or success 130 CSVRecordEvent.clear_tests()
Before each test, reset cell sims / BMS and set appropriate temperatures. After each test, clean up modified objects.
Fixture arguments are provided in an abnormal way, see below tests for details on how to provide these arguments. A default value is used if neither soc nor volts is provided.
Parameters
- float temperature: the initial temperature in C
- float soc: the initial state of charge
- float volts: the initial voltage
133def standard_charge( 134 charge_current: float = 2, 135 max_time: int = 8 * 3600, 136 sample_interval: int = 10, 137 minimum_readings: int = 3, 138 termination_current: float = 0.100, 139): 140 """ 141 Helper function to charge batteries in accordance with 4.3.1 for not greater than three hours. 142 4.3.1 = 23 ± 5°C (73.4°F) ambient pressure/relative humidity, with 2+ hours between charge and discharge. 143 """ 144 _bms.voltage = 16.8 145 _bms.ov_protection = _bms.voltage + 0.050 # 50mV above the charging voltage 146 _bms.current = charge_current 147 _bms.termination_current = termination_current # 100 mA 148 _bms.max_time = max_time 149 _bms.sample_interval = sample_interval 150 _bms.minimum_readings = minimum_readings 151 152 # Run the Charge cycle 153 _plateset.ce_switch = True 154 _bms.run_li_charge_cycle() 155 _plateset.ce_switch = False
Helper function to charge batteries in accordance with 4.3.1 for not greater than three hours. 4.3.1 = 23 ± 5°C (73.4°F) ambient pressure/relative humidity, with 2+ hours between charge and discharge.
158def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10): 159 """Helper function to stabilize the batteries for 2+ hours.""" 160 _bms.max_time = seconds 161 _bms.sample_interval = sample_interval 162 _bms.run_resting_cycle()
Helper function to stabilize the batteries for 2+ hours.
165def standard_discharge( 166 discharge_current: float = 2, max_time: int = 8 * 3600, sample_interval: int = 10, discharge_voltage: float = 10 167): 168 """Helper function to discharge at 2A until 10V.""" 169 _bms.voltage = discharge_voltage 170 _bms.uv_protection = _bms.voltage - 0.500 # 500mV below voltage cutoff 171 _bms.current = discharge_current 172 _bms.discharge_type = DischargeType.CONSTANT_CURRENT 173 _bms.max_time = max_time 174 _bms.sample_interval = sample_interval 175 176 # Run the discharge cycle, returning the capacity 177 capacity = _bms.run_discharge_cycle() 178 logger.write_info_to_report(f"Discharge complete, capacity was {capacity * 1000.0} mAh") 179 return capacity
Helper function to discharge at 2A until 10V.
182def test_fault_enable(): 183 """ 184 | Description | Enable faults via SMBus. | 185 | :------------------- | :--------------------------------------------------------------------- | 186 | GitHub Issue | turnaroundfactor/HITL#476 | 187 | Instructions | 1. Enable faults via SMBus </br>\ 188 2. Raise an over-temp fault </br>\ 189 3. Clear the over-temp fault | 190 | Pass / Fail Criteria | Pass if faults can be raised | 191 | Estimated Duration | 1 minute | 192 """ 193 194 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.FAULT_ENABLE) 195 time.sleep(FLASH_SLEEP) 196 197 # Check if overtemp faults can be raised. 198 timeout_s = 30 199 200 # Raise a fault 201 _plateset.thermistor1 = 65 202 start = time.perf_counter() 203 while (serial_data := serial_monitor.read(latest=True)) and not serial_data["flags.fault_overtemp_discharge"]: 204 if time.perf_counter() - start > timeout_s: 205 message = f"Over-temperature fault was not raised after {timeout_s} seconds." 206 logger.write_failure_to_html_report(message) 207 raise TimeoutError(message) 208 logger.write_result_to_html_report("Fault successfully raised.") 209 210 # Clear the fault 211 _plateset.thermistor1 = 45 212 start = time.perf_counter() 213 while (serial_data := serial_monitor.read(latest=True)) and serial_data["flags.fault_overtemp_discharge"]: 214 if time.perf_counter() - start > timeout_s: 215 message = f"Over-temperature fault was not cleared after {timeout_s} seconds." 216 logger.write_failure_to_html_report(message) 217 raise TimeoutError(message) 218 logger.write_result_to_html_report("Fault successfully cleared.")
| Description | Enable faults via SMBus. |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Instructions | 1. Enable faults via SMBus 2. Raise an over-temp fault 3. Clear the over-temp fault |
| Pass / Fail Criteria | Pass if faults can be raised |
| Estimated Duration | 1 minute |
221class TestCalibration: 222 """Calibrate the BMS if needed.""" 223 224 average = 0 225 readings = 0 226 227 def bms_current(self): 228 """Measure serial current and calculate an average.""" 229 assert (serial_date := serial_monitor.read()), "Could not read serial." 230 new_reading = serial_date["mamps"] 231 self.average = (new_reading + self.readings * self.average) / (self.readings + 1) 232 self.readings += 1 233 logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}") # Output current on every sample 234 235 def test_calibration(self): 236 """ 237 | Description | Test calibration retention | 238 | :------------------- | :--------------------------------------------------------------------- | 239 | GitHub Issue | turnaroundfactor/HITL#413 | 240 | Instructions | 1. Calibrate the BMS </br>\ 241 2. Confirm BMS is calibrated | 242 | Pass / Fail Criteria | Pass if calibrated | 243 | Estimated Duration | 1 minute | 244 """ 245 acceptable_error_ma = 5 246 scan_count = 6 # How many measurements to take for an average. 247 248 self.readings = 0 # Reset average 249 for _ in range(scan_count): # Measure current over some period 250 self.bms_current() 251 time.sleep(5) 252 logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA") 253 offset = int(round(self.average, 0)) 254 logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA") 255 data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE 256 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data) 257 time.sleep(FLASH_SLEEP) 258 data = cast(int, _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]) 259 logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}") 260 261 # Confirm current is in acceptable range 262 self.readings = 0 # Reset average 263 for _ in range(scan_count): 264 self.bms_current() 265 time.sleep(5) 266 logger.write_result_to_html_report(f"Average rest current (calibrated): {self.average:.3f} mA") 267 assert ( 268 acceptable_error_ma > self.average > -acceptable_error_ma 269 ), f"{self.average:.3f} mA outside limit of ±{acceptable_error_ma:.3f} mA"
Calibrate the BMS if needed.
227 def bms_current(self): 228 """Measure serial current and calculate an average.""" 229 assert (serial_date := serial_monitor.read()), "Could not read serial." 230 new_reading = serial_date["mamps"] 231 self.average = (new_reading + self.readings * self.average) / (self.readings + 1) 232 self.readings += 1 233 logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}") # Output current on every sample
Measure serial current and calculate an average.
235 def test_calibration(self): 236 """ 237 | Description | Test calibration retention | 238 | :------------------- | :--------------------------------------------------------------------- | 239 | GitHub Issue | turnaroundfactor/HITL#413 | 240 | Instructions | 1. Calibrate the BMS </br>\ 241 2. Confirm BMS is calibrated | 242 | Pass / Fail Criteria | Pass if calibrated | 243 | Estimated Duration | 1 minute | 244 """ 245 acceptable_error_ma = 5 246 scan_count = 6 # How many measurements to take for an average. 247 248 self.readings = 0 # Reset average 249 for _ in range(scan_count): # Measure current over some period 250 self.bms_current() 251 time.sleep(5) 252 logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA") 253 offset = int(round(self.average, 0)) 254 logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA") 255 data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE 256 _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data) 257 time.sleep(FLASH_SLEEP) 258 data = cast(int, _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]) 259 logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}") 260 261 # Confirm current is in acceptable range 262 self.readings = 0 # Reset average 263 for _ in range(scan_count): 264 self.bms_current() 265 time.sleep(5) 266 logger.write_result_to_html_report(f"Average rest current (calibrated): {self.average:.3f} mA") 267 assert ( 268 acceptable_error_ma > self.average > -acceptable_error_ma 269 ), f"{self.average:.3f} mA outside limit of ±{acceptable_error_ma:.3f} mA"
| Description | Test calibration retention |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#413 |
| Instructions | 1. Calibrate the BMS 2. Confirm BMS is calibrated |
| Pass / Fail Criteria | Pass if calibrated |
| Estimated Duration | 1 minute |
272def test_charge_enable_on(): 273 """ 274 | Description | Confirm charging above 400mA works when CE is active | 275 | :------------------- | :--------------------------------------------------------------- | 276 | GitHub Issue | turnaroundfactor/HITL#342 | 277 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 278 | MIL-PRF Requirements | The charge enable terminal shall comply with the following: </br>\ 279 ⠀⠀a. Maximum charge without enable: 400 mA </br>\ 280 ⠀⠀b. Equivalent resistor: 235 Ω </br>\ 281 ⠀⠀c. Equivalent diode VF: 1.3 V </br>\ 282 ⠀⠀d. Approximate activation current: 7 mA | 283 | Instructions | 1. Activate CE </br>\ 284 2. Charge at 2A | 285 | Pass / Fail Criteria | Pass if current is more than 400mA | 286 | Estimated Duration | 1 minute | 287 | Note | This test can fail if the battery is sufficiently charged. | 288 """ 289 passing_current = 0.400 290 291 # Enable charging and timer 292 _plateset.ce_switch = True 293 _bms.charger.set_profile(volts=16.8, amps=2) 294 _bms.charger.enable() 295 _bms.timer.reset() # Keep track of runtime 296 297 # Charge until current is more than passing_current or timeout 298 timeout_seconds = 10 299 while (latest_current := _bms.charger.amps) < passing_current and _bms.timer.elapsed_time <= timeout_seconds: 300 logger.write_info_to_report( 301 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 302 f"Current: {latest_current:.3f}" 303 ) 304 time.sleep(1) 305 306 # Charging is complete, turn off the charger 307 _bms.charger.disable() 308 _plateset.ce_switch = False 309 310 # Check results 311 logger.write_result_to_html_report(f"Current: {latest_current:.3f} A ≥ {passing_current:.3f} A") 312 if latest_current < passing_current: 313 pytest.fail(f"Current of {latest_current:.3f} A does not exceed {passing_current:.3f} A.")
| Description | Confirm charging above 400mA works when CE is active |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#342 |
| MIL-PRF Section | 3.5.6.2 (Charge Enable) |
| MIL-PRF Requirements | The charge enable terminal shall comply with the following: ⠀⠀a. Maximum charge without enable: 400 mA ⠀⠀b. Equivalent resistor: 235 Ω ⠀⠀c. Equivalent diode VF: 1.3 V ⠀⠀d. Approximate activation current: 7 mA |
| Instructions | 1. Activate CE 2. Charge at 2A |
| Pass / Fail Criteria | Pass if current is more than 400mA |
| Estimated Duration | 1 minute |
| Note | This test can fail if the battery is sufficiently charged. |
316def test_charge_enable_off(): 317 """ 318 | Description | Confirm charging above 400mA doesn't work when CE is inactive | 319 | :------------------- | :--------------------------------------------------------------------- | 320 | GitHub Issue | turnaroundfactor/HITL#342 | 321 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 322 | MIL-PRF Requirements | The charge enable terminal shall comply with the following: </br>\ 323 ⠀⠀a. Maximum charge without enable: 400 mA </br>\ 324 ⠀⠀b. Equivalent resistor: 235 Ω </br>\ 325 ⠀⠀c. Equivalent diode VF: 1.3 V </br>\ 326 ⠀⠀d. Approximate activation current: 7 mA | 327 | Instructions | 1. Increment charge current every second </br>\ 328 2. Stop when charge current drops to ~0A | 329 | Pass / Fail Criteria | Pass if the highest current is less than 400mA | 330 | Estimated Duration | 5 minutes | 331 """ 332 failing_current = 0.400 333 uncertainty = 0.005 334 335 # Enable charging and timer 336 _plateset.ce_switch = False 337 _bms.charger.set_profile(volts=16.8, amps=0.010) # Starting current 338 _bms.charger.enable() 339 _bms.timer.reset() # Keep track of runtime 340 341 # Charge until current drops below some threshold (can float above 0) or is more than failing_current 342 max_charge_current = 0 343 while ( 344 _bms.charger.target_amps <= failing_current + uncertainty 345 and abs(_bms.charger.target_amps - (latest_current := _bms.charger.amps)) <= uncertainty 346 ): 347 # while (failing_current + uncertainty * 2) > (latest_current := _bms.charger.amps) > 0.005: 348 max_charge_current = max(max_charge_current, latest_current) 349 logger.write_info_to_report( 350 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 351 f"Current: {latest_current:.3f}" 352 ) 353 # Increase by 1 mA / second 354 _bms.charger.set_profile(volts=_bms.charger.target_volts, amps=_bms.charger.target_amps + 0.001) 355 time.sleep(1) 356 357 # Charging is complete, turn off the charger 358 _bms.charger.disable() 359 360 # Check results 361 logger.write_result_to_html_report(f"Current: {max_charge_current:.3f} A ≤ {failing_current:.3f} ± {uncertainty} A") 362 if max_charge_current > failing_current + uncertainty: 363 pytest.fail(f"Current of {max_charge_current:.3f} A exceeds {failing_current:.3f} A limit.")
| Description | Confirm charging above 400mA doesn't work when CE is inactive |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#342 |
| MIL-PRF Section | 3.5.6.2 (Charge Enable) |
| MIL-PRF Requirements | The charge enable terminal shall comply with the following: ⠀⠀a. Maximum charge without enable: 400 mA ⠀⠀b. Equivalent resistor: 235 Ω ⠀⠀c. Equivalent diode VF: 1.3 V ⠀⠀d. Approximate activation current: 7 mA |
| Instructions | 1. Increment charge current every second 2. Stop when charge current drops to ~0A |
| Pass / Fail Criteria | Pass if the highest current is less than 400mA |
| Estimated Duration | 5 minutes |
366def test_taf_charge_enable_off(): 367 """ 368 | Description | TAF: Confirm charging above 20mA doesn't work when CE is inactive | 369 | :------------------- | :--------------------------------------------------------------------- | 370 | GitHub Issue | turnaroundfactor/HITL#342 | 371 | MIL-PRF Section | 3.5.6.2 (Charge Enable) | 372 | Instructions | 1. Increment charge current every second </br>\ 373 2. Stop when charge current drops to ~0A | 374 | Pass / Fail Criteria | Pass if the highest current is less than or equal to 20mA | 375 | Estimated Duration | 1 minute | 376 | Note | We want to disable if current is over 20 mA since \ 377 that's what BT does, and it's a safer way to charge. Other OTS \ 378 may have different cutoffs, and the BT one was measured by experiment. | 379 """ 380 target_current = 0.020 381 uncertainty = 0.005 382 383 # Enable charging and timer 384 _plateset.ce_switch = False 385 _bms.charger.set_profile(volts=16.8, amps=0.010) # Starting current 386 _bms.charger.enable() 387 _bms.timer.reset() # Keep track of runtime 388 389 # Charge until current drops below some threshold (can float above 0) or is more than failing_current 390 max_charge_current = 0 391 while ( 392 _bms.charger.target_amps <= target_current + uncertainty 393 and abs(_bms.charger.target_amps - (latest_current := _bms.charger.amps)) <= uncertainty 394 ): 395 max_charge_current = max(max_charge_current, latest_current) 396 logger.write_info_to_report( 397 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {_bms.dmm.volts:.3f}, " 398 f"Current: {latest_current:.3f}" 399 ) 400 # Increase by 1 mA / second 401 _bms.charger.set_profile(volts=_bms.charger.target_volts, amps=_bms.charger.target_amps + 0.001) 402 time.sleep(1) 403 404 # Charging is complete, turn off the charger 405 _bms.charger.disable() 406 407 # Check results 408 logger.write_result_to_html_report(f"Current: {max_charge_current:.3f} A = {target_current:.3f} ± {uncertainty} A") 409 if not target_current - uncertainty <= max_charge_current <= target_current + uncertainty: 410 pytest.fail(f"Current of {max_charge_current:.3f} A exceeds {target_current:.3f} A limit.")
| Description | TAF: Confirm charging above 20mA doesn't work when CE is inactive |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#342 |
| MIL-PRF Section | 3.5.6.2 (Charge Enable) |
| Instructions | 1. Increment charge current every second 2. Stop when charge current drops to ~0A |
| Pass / Fail Criteria | Pass if the highest current is less than or equal to 20mA |
| Estimated Duration | 1 minute |
| Note | We want to disable if current is over 20 mA since that's what BT does, and it's a safer way to charge. Other OTS may have different cutoffs, and the BT one was measured by experiment. |
413@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 414class TestExtendedCycle: 415 """Perform a long charge / discharge.""" 416 417 class CellVoltageDiscrepancy(CSVRecordEvent): 418 """@private Compare cell sim voltage to reported cell voltage.""" 419 420 allowable_error = 0.01 421 max = SimpleNamespace(cell_id=0, sim_v=0, bms_v=0, error=0.0) 422 423 @classmethod 424 def failed(cls) -> bool: 425 """Check if test parameters were exceeded.""" 426 return bool(cls.max.error > cls.allowable_error) 427 428 @classmethod 429 def verify(cls, row, serial_data, _cell_data): 430 """Cell voltage within range""" 431 for i, cell_id in enumerate(_bms.cells): 432 row_data = SimpleNamespace( 433 cell_id=cell_id, 434 sim_v=row[f"ADC Plate Cell {cell_id} Voltage (V)"], 435 bms_v=serial_data[f"mvolt_cell{'' if i == 0 else i}"] / 1000, 436 ) 437 row_data.error = abs((row_data.bms_v - row_data.sim_v) / row_data.sim_v) 438 cls.max = max(cls.max, row_data, key=lambda data: data.error) 439 440 @classmethod 441 def result(cls): 442 """Detailed test result information.""" 443 return ( 444 f"Cell Voltage error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 445 f"(Sim {cls.max.cell_id}: {cls.max.sim_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 446 ) 447 448 class CurrentDiscrepancy(CSVRecordEvent): 449 """@private Compare terminal current to reported current.""" 450 451 allowable_error = 0.015 452 max = SimpleNamespace(hitl_a=0, bms_a=0, error=0.0) 453 454 @classmethod 455 def failed(cls) -> bool: 456 """Check if test parameters were exceeded.""" 457 return bool(cls.max.error > cls.allowable_error) 458 459 @classmethod 460 def verify(cls, row, serial_data, _cell_data): 461 """Current within range""" 462 row_data = SimpleNamespace(hitl_a=row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 463 row_data.error = abs((row_data.bms_a - row_data.hitl_a) / row_data.hitl_a) 464 if abs(row_data.hitl_a) > 0.100: # Ignore currents within 100mA to -100mA 465 cls.max = max(cls.max, row_data, key=lambda data: data.error) 466 467 @classmethod 468 def result(cls): 469 """Detailed test result information.""" 470 return ( 471 f"Current error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 472 f"(HITL: {cls.max.hitl_a * 1000:.1f} mA, BMS: {cls.max.bms_a * 1000:.1f} mA)" 473 ) 474 475 class TerminalVoltageDiscrepancy(CSVRecordEvent): 476 """@private Compare HITL voltage to reported Terminal voltage.""" 477 478 allowable_error = 0.015 479 max = SimpleNamespace(hitl_v=0, bms_v=0, error=0.0) 480 481 @classmethod 482 def failed(cls) -> bool: 483 """Check if test parameters were exceeded.""" 484 return bool(cls.max.error > cls.allowable_error) 485 486 @classmethod 487 def verify(cls, row, serial_data, _cell_data): 488 """Terminal voltage within range""" 489 row_data = SimpleNamespace(hitl_v=row["HITL Voltage (V)"], bms_v=serial_data["mvolt_terminal"] / 1000) 490 row_data.error = abs((row_data.bms_v - row_data.hitl_v) / row_data.hitl_v) 491 cls.max = max(cls.max, row_data, key=lambda data: data.error) 492 493 @classmethod 494 def result(cls): 495 """Detailed test result information.""" 496 return ( 497 f"Terminal Voltage error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 498 f"(HITL: {cls.max.hitl_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 499 ) 500 501 class TemperatureDiscrepancyTherm1(CSVRecordEvent): 502 """@private Compare HITL temperature to reported temperature.""" 503 504 allowable_error = 5.0 505 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 506 507 @classmethod 508 def failed(cls) -> bool: 509 """Check if test parameters were exceeded.""" 510 return bool(cls.max.error > cls.allowable_error) 511 512 @classmethod 513 def verify(cls, _row, serial_data, _cell_data): 514 """Temperature within range""" 515 row_data = SimpleNamespace(hitl_c=_plateset.thermistor1, bms_c=serial_data["dk_temp"] / 10 - 273) 516 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 517 cls.max = max(cls.max, row_data, key=lambda data: data.error) 518 519 @classmethod 520 def result(cls): 521 """Detailed test result information.""" 522 return ( 523 f"Thermistor 1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 524 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 525 ) 526 527 class TemperatureDiscrepancyTherm2(CSVRecordEvent): 528 """@private Compare HITL temperature to reported temperature.""" 529 530 allowable_error = 5.0 531 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 532 533 @classmethod 534 def failed(cls) -> bool: 535 """Check if test parameters were exceeded.""" 536 return bool(cls.max.error > cls.allowable_error) 537 538 @classmethod 539 def verify(cls, _row, serial_data, _cell_data): 540 """Temperature within range""" 541 row_data = SimpleNamespace(hitl_c=_plateset.thermistor2, bms_c=serial_data["dk_temp1"] / 10 - 273) 542 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 543 cls.max = max(cls.max, row_data, key=lambda data: data.error) 544 545 @classmethod 546 def result(cls): 547 """Detailed test result information.""" 548 return ( 549 f"Thermistor 2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 550 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 551 ) 552 553 class NoFaults(CSVRecordEvent): 554 """@private Check for any faults.""" 555 556 faults: list[str] = [] 557 558 @classmethod 559 def failed(cls) -> bool: 560 """Check if test parameters were exceeded.""" 561 return len(cls.faults) != 0 562 563 @classmethod 564 def verify(cls, _row, serial_data, _cell_data): 565 """Check for faults.""" 566 for key in serial_data: 567 if "fault" in key and key.startswith("flags.") and serial_data[key]: 568 cls.faults.append(key.removeprefix("flags.").title()) 569 570 @classmethod 571 def result(cls): 572 """Detailed test result information.""" 573 return f"Faults encountered: {' | '.join(cls.faults) or None}" 574 575 class NoReset(CSVRecordEvent): 576 """@private Check for resets.""" 577 578 reset_reasons = ControlStatusRegister(0) 579 580 @classmethod 581 def failed(cls) -> bool: 582 """Check if test parameters were exceeded.""" 583 return bool(cls.reset_reasons) 584 585 @classmethod 586 def verify(cls, _row, serial_data, _cell_data): 587 """Check if any resets occurred.""" 588 reset_reason = ControlStatusRegister(serial_data.get("Reset_Flags", 0)) 589 reset_reason &= ~ControlStatusRegister.POWER & ~ControlStatusRegister.RESET_PIN # Ignore valid reasons 590 cls.reset_reasons |= reset_reason 591 592 @classmethod 593 def result(cls): 594 """Detailed test result information.""" 595 return f"Resets encountered: {str(cls.reset_reasons) or None}" 596 597 class SOCDiscrepancy(CSVRecordEvent): 598 """@private Compare lowest HITL cell SOC to reported SOC.""" 599 600 allowable_error = 0.05 601 max = SimpleNamespace(sim_id=0, sim_soc=0, bms_soc=0, error=0.0) 602 603 @classmethod 604 def failed(cls) -> bool: 605 """Check if test parameters were exceeded.""" 606 return bool(cls.max.error > cls.allowable_error) 607 608 @classmethod 609 def verify(cls, _row, serial_data, cell_data): 610 """SOC within range.""" 611 lowest_sim_soc_id = min(cell_data, key=lambda cell_id: cell_data[cell_id]["state_of_charge"]) 612 lowest_sim_soc = cell_data[lowest_sim_soc_id]["state_of_charge"] 613 row_data = SimpleNamespace( 614 sim_id=lowest_sim_soc_id, sim_soc=lowest_sim_soc, bms_soc=serial_data["percent_charged"] / 100 615 ) 616 row_data.error = abs(row_data.bms_soc - row_data.sim_soc) 617 cls.max = max(cls.max, row_data, key=lambda data: data.error) 618 619 @classmethod 620 def result(cls): 621 """Detailed test result information.""" 622 return ( 623 f"SOC error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 624 f"(Sim {cls.max.sim_id}: {cls.max.sim_soc:.1%}, BMS: {cls.max.bms_soc:.1%})" 625 ) 626 627 class HealthChangeCount(CSVRecordEvent): 628 """@private Check how many times Health changes.""" 629 630 changes = 0 631 allowable_changes = 1 632 last_reported_health: int | None = None 633 634 @classmethod 635 def failed(cls) -> bool: 636 """Check if test parameters were exceeded.""" 637 return bool(cls.changes > cls.allowable_changes) 638 639 @classmethod 640 def verify(cls, _row, serial_data, _cell_data): 641 """Detect health change.""" 642 if serial_data["percent_health"] != cls.last_reported_health: 643 cls.changes += cls.last_reported_health is not None 644 cls.last_reported_health = serial_data["percent_health"] 645 646 @classmethod 647 def result(cls): 648 """Detailed test result information.""" 649 return f"Health change#: {cls.cmp(cls.changes, '<=', cls.allowable_changes, form='d')}" 650 651 class HealthChange(CSVRecordEvent): 652 """@private Check health change.""" 653 654 allowable_change = 0.01 655 max_change: float = 0.0 656 initial_health: float | None = None 657 658 @classmethod 659 def failed(cls) -> bool: 660 """Check if test parameters were exceeded.""" 661 return bool(cls.max_change > cls.allowable_change) 662 663 @classmethod 664 def verify(cls, _row, serial_data, _cell_data): 665 """Health change within range.""" 666 if cls.initial_health is None: 667 cls.initial_health = serial_data["percent_health"] / 100 668 cls.max_change = max(cls.max_change, abs(cls.initial_health - serial_data["percent_health"] / 100)) 669 670 @classmethod 671 def result(cls): 672 """Detailed test result information.""" 673 return f"Health change: {cls.cmp(cls.max_change, '<=', cls.allowable_change)}" 674 675 class UsedAhDiscrepancy(CSVRecordEvent): 676 """@private Compare HITL used Ah to reported used Ah.""" 677 678 allowable_error = 0.01 679 max = SimpleNamespace(hitl_ah=0, bms_ah=0, error=0.0) 680 initial_charge_cycle: int | None = None 681 682 @classmethod 683 def failed(cls) -> bool: 684 """Check if test parameters were exceeded.""" 685 return bool(cls.max.error > cls.allowable_error) 686 687 @classmethod 688 def verify(cls, row, serial_data, _cell_data): 689 """Check used Ah during discharge.""" 690 if cls.initial_charge_cycle is None: 691 cls.initial_charge_cycle = serial_data["charge_cycles"] 692 delta_cycle = serial_data["charge_cycles"] - cls.initial_charge_cycle 693 row_data = SimpleNamespace( 694 hitl_ah=-(row["HITL Capacity (Ah)"] or 0), 695 bms_ah=(serial_data["milliamp_hour_used"] + delta_cycle * serial_data["milliamp_hour_capacity"]) / 1000, 696 ) 697 if row["Cycle"] == "run_discharge_cycle" and row_data.hitl_ah > 0.100: 698 logger.write_debug_to_report(f" In discharge: {row_data.hitl_ah} Ah") 699 row_data.error = abs((row_data.bms_ah - row_data.hitl_ah) / row_data.hitl_ah) 700 cls.max = max(cls.max, row_data, key=lambda data: data.error) 701 702 @classmethod 703 def result(cls): 704 """Detailed test result information.""" 705 return ( 706 f"Used Ah error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 707 f"(HITL: {cls.max.hitl_ah * 1000:.1f} mAh, BMS: {cls.max.bms_ah * 1000:.1f} mAh)" 708 ) 709 710 class ChargeCycle(CSVRecordEvent): 711 """@private Compare cell sim voltage to reported cell voltage.""" 712 713 allowable_cycles = 1 714 initial_cycle: int | None = None 715 max_cycle = 0 716 717 @classmethod 718 def failed(cls) -> bool: 719 """Check if test parameters were exceeded.""" 720 return cls.initial_cycle is None or bool(cls.max_cycle - cls.initial_cycle > cls.allowable_cycles) 721 722 @classmethod 723 def verify(cls, _row, serial_data, _cell_data): 724 """Cell voltage within range""" 725 if cls.initial_cycle is None: 726 cls.initial_cycle = serial_data["charge_cycles"] 727 cls.max_cycle = max(cls.max_cycle, serial_data["charge_cycles"]) 728 729 @classmethod 730 def result(cls): 731 """Detailed test result information.""" 732 return ( 733 f"Cycle change: " 734 f"{cls.cmp(cls.max_cycle - (cls.initial_cycle or 0), '<=', cls.allowable_cycles, form='d')} " 735 f"(Starting: {cls.initial_cycle}, Ending: {cls.max_cycle})" 736 ) 737 738 def test_extended_cycle(self): 739 """ 740 | Description | Perform a long charge / discharge | 741 | :------------------- | :--------------------------------------------------------------------- | 742 | GitHub Issue | turnaroundfactor/HITL#342 | 743 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 744jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 745 | MIL-PRF Sections | 4.7.2.3 (Capacity discharge) </br>\ 746 4.6.1 (Standard Charge) </br>\ 747 4.3.1 (Normal conditions) </br>\ 748 3.5.3 (Capacity) | 749 | Instructions | 1. Set thermistors to 23C </br>\ 750 2. Put cells in a rested state at 2.5V per cell </br>\ 751 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 752 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours) </br>\ 753 5. Discharge at 2A until battery reaches 10V then stop discharging | 754 | Pass / Fail Criteria | ⦁ Serial cell voltage agrees with HITL cell voltage to within 1% </br>\ 755 ⦁ Serial terminal current agrees with HITL current to within 1% </br>\ 756 ⦁ Serial terminal voltage agrees with HITL voltage to within 1% </br>\ 757 ⦁ Serial thermistor 1 and 2 agree with HITL thermistor 1 and 2 to within 5°C </br>\ 758 ⦁ No Fault Flags over entire duration of test </br>\ 759 ⦁ No resets occur over entire duration of test </br>\ 760 ⦁ Serial cell SOC agrees with lowest HITL cell SOC to within 5% SOC </br>\ 761 ⦁ Serial Health shall only change once </br>\ 762 ⦁ Serial Health shall not change by more than 1% </br>\ 763 ⦁ Serial used Ah agrees with HITL Ah to within 1% (for abs(HITL Ah > 100mAh)) </br>\ 764 ⦁ Serial charge cycle shall only increment once </br>\ 765 ⦁ E-ink display is operational [TBD How to do this] | 766 | Estimated Duration | 12 hours | 767 | Note | MIL-PRF 4.7.2.3.1 (Initial capacity discharge): Each battery subjected \ 768 to the capacity discharge test above (see 4.7.2.3) on its initial \ 769 charge/discharge cycle is permitted up to three cycles to meet the \ 770 capacity discharge test requirement (see 3.1). Any battery not meeting \ 771 the specified capacity discharge requirement (see 3.1) during any of \ 772 its first three cycles is considered a failure | 773 """ 774 # FIXME(JA): adjust estimated duration based on first test 775 776 standard_charge() 777 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 778 standard_discharge() 779 780 # Check results 781 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 782 pytest.fail(CSVRecordEvent.result())
Perform a long charge / discharge.
738 def test_extended_cycle(self): 739 """ 740 | Description | Perform a long charge / discharge | 741 | :------------------- | :--------------------------------------------------------------------- | 742 | GitHub Issue | turnaroundfactor/HITL#342 | 743 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 744jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 745 | MIL-PRF Sections | 4.7.2.3 (Capacity discharge) </br>\ 746 4.6.1 (Standard Charge) </br>\ 747 4.3.1 (Normal conditions) </br>\ 748 3.5.3 (Capacity) | 749 | Instructions | 1. Set thermistors to 23C </br>\ 750 2. Put cells in a rested state at 2.5V per cell </br>\ 751 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 752 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours) </br>\ 753 5. Discharge at 2A until battery reaches 10V then stop discharging | 754 | Pass / Fail Criteria | ⦁ Serial cell voltage agrees with HITL cell voltage to within 1% </br>\ 755 ⦁ Serial terminal current agrees with HITL current to within 1% </br>\ 756 ⦁ Serial terminal voltage agrees with HITL voltage to within 1% </br>\ 757 ⦁ Serial thermistor 1 and 2 agree with HITL thermistor 1 and 2 to within 5°C </br>\ 758 ⦁ No Fault Flags over entire duration of test </br>\ 759 ⦁ No resets occur over entire duration of test </br>\ 760 ⦁ Serial cell SOC agrees with lowest HITL cell SOC to within 5% SOC </br>\ 761 ⦁ Serial Health shall only change once </br>\ 762 ⦁ Serial Health shall not change by more than 1% </br>\ 763 ⦁ Serial used Ah agrees with HITL Ah to within 1% (for abs(HITL Ah > 100mAh)) </br>\ 764 ⦁ Serial charge cycle shall only increment once </br>\ 765 ⦁ E-ink display is operational [TBD How to do this] | 766 | Estimated Duration | 12 hours | 767 | Note | MIL-PRF 4.7.2.3.1 (Initial capacity discharge): Each battery subjected \ 768 to the capacity discharge test above (see 4.7.2.3) on its initial \ 769 charge/discharge cycle is permitted up to three cycles to meet the \ 770 capacity discharge test requirement (see 3.1). Any battery not meeting \ 771 the specified capacity discharge requirement (see 3.1) during any of \ 772 its first three cycles is considered a failure | 773 """ 774 # FIXME(JA): adjust estimated duration based on first test 775 776 standard_charge() 777 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 778 standard_discharge() 779 780 # Check results 781 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 782 pytest.fail(CSVRecordEvent.result())
| Description | Perform a long charge / discharge |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#342 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.2.3 (Capacity discharge) 4.6.1 (Standard Charge) 4.3.1 (Normal conditions) 3.5.3 (Capacity) |
| Instructions | 1. Set thermistors to 23C 2. Put cells in a rested state at 2.5V per cell 3. Charge battery (16.8V / 3A / 100 mA cutoff) 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours) 5. Discharge at 2A until battery reaches 10V then stop discharging |
| Pass / Fail Criteria | ⦁ Serial cell voltage agrees with HITL cell voltage to within 1% ⦁ Serial terminal current agrees with HITL current to within 1% ⦁ Serial terminal voltage agrees with HITL voltage to within 1% ⦁ Serial thermistor 1 and 2 agree with HITL thermistor 1 and 2 to within 5°C ⦁ No Fault Flags over entire duration of test ⦁ No resets occur over entire duration of test ⦁ Serial cell SOC agrees with lowest HITL cell SOC to within 5% SOC ⦁ Serial Health shall only change once ⦁ Serial Health shall not change by more than 1% ⦁ Serial used Ah agrees with HITL Ah to within 1% (for abs(HITL Ah > 100mAh)) ⦁ Serial charge cycle shall only increment once ⦁ E-ink display is operational [TBD How to do this] |
| Estimated Duration | 12 hours |
| Note | MIL-PRF 4.7.2.3.1 (Initial capacity discharge): Each battery subjected to the capacity discharge test above (see 4.7.2.3) on its initial charge/discharge cycle is permitted up to three cycles to meet the capacity discharge test requirement (see 3.1). Any battery not meeting the specified capacity discharge requirement (see 3.1) during any of its first three cycles is considered a failure |
785@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7}], indirect=True) 786class TestSMBusWritableRegisters: 787 """SMBus Writable Registers""" 788 789 def test_smbus_writable_registers(self) -> None: 790 """ 791 | Description | Validate SMBus Writable Registers | 792 | :------------------- | :--------------------------------------------------------------------- | 793 | GitHub Issue | turnaroundfactor/HITL#397 | 794 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 795jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 796 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 797 | Instructions | 1. Set thermistors to 23C </br>\ 798 2. Put cells in a rested state at 3.7V per cell </br>\ 799 3. Host to Battery Read Word (Register 1): 0x00 </br>\ 800 5. Host to Battery Read Word (Register 2): 0x0438 </br>\ 801 6. Host to Battery Write Word (Register 2): 0xBEEF </br>\ 802 7. Host to Battery Read Word (Register 2): 0xBEEF </br>\ 803 8. Host to Battery Read Word (Register 3): 0x000A </br>\ 804 9. Host to Battery Write Word (Register 3): 0xBEEF </br>\ 805 10. Host to Battery Read Word (Register 3): 0xBEEF </br>\ 806 11. Host to Battery Write Word (Register 3): 0x000A </br>\ 807 12. Host to Battery Read Word (Register 3): 0x000A </br>\ 808 13. Host to Battery Read Word (Register 4): 000xx00x0010 (where x is "don't care") | 809 | Pass / Fail Criteria | ⦁ ManufacturerAccess (Battery Register 1) returns 0x00 </br>\ 810 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0x0438 </br>\ 811 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0xBEEF </br>\ 812 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 813 ⦁ Remaining Time Alarm (Battery Register 3) returns 0xBEEF </br>\ 814 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 815 ⦁ Battery Mode (Battery Register 4) returns 000xx00x0010 | 816 | Estimated Duration | 30 seconds | 817 | Note | When specified (see 3.1), batteries shall be compliant with System \ 818 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 819 Data (SBData) Specification, version 1.1, with the exception that \ 820 SBData safety signal hardware requirements therein shall be replaced \ 821 with a charge enable when a charge enable is specified (see 3.1 and \ 822 3.5.6). Certification is required. Batteries shall be compatible \ 823 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 824 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 825 accurate within +0/-5% of the actual state of charge for the battery \ 826 under test throughout the discharge. Manufacturer and battery data \ 827 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 828 logic circuitry. Pull-up resistors will be provided by the charger. \ 829 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 830 Smart batteries may act as master or slave on the bus, but must \ 831 perform bus master timing arbitration according to the SMBus \ 832 specification when acting as master. | 833 """ 834 time.sleep(30) 835 836 assert (serial_data := serial_monitor.read()) 837 ten_percent_capacity = float(serial_data["milliamp_hour_capacity"]) // 10 838 default_value = 0x000A 839 easy_spot_value = 0xBEEF 840 smbus_registers = [ 841 {"sm_register": SMBusReg.MANUFACTURING_ACCESS, "data": [0]}, 842 { 843 "sm_register": SMBusReg.REMAINING_CAPACITY_ALARM, 844 "data": [ten_percent_capacity, easy_spot_value], 845 }, 846 { 847 "sm_register": SMBusReg.REMAINING_TIME_ALARM, 848 "data": [default_value, easy_spot_value, default_value], 849 }, 850 ] 851 failed_tests = [] 852 853 # Manufacturing Access, Remaining Capacity Alarm, Remaining Time Alarm 854 for smbus_register in smbus_registers: 855 register = cast(SMBusReg, smbus_register["sm_register"]) 856 index = 0 857 for elem in map(int, cast(list[int], smbus_register["data"])): 858 if index > 0: 859 logger.write_info_to_report(f"Writing {hex(elem)} to {register.fname}") 860 _smbus.write_register(register, elem) 861 862 read_sm_response = _smbus.read_register(register) 863 864 expected_value = elem.to_bytes(2, byteorder="little") 865 if read_sm_response[1] != expected_value: 866 logger.write_result_to_html_report( 867 f"{register.fname} did not have expected result of {hex(elem)}, " 868 f"instead was: {read_sm_response[1]!r}" 869 ) 870 logger.write_warning_to_report( 871 f"{register.fname} did not have expected result of {hex(elem)}, " 872 f"instead was: {read_sm_response[1]!r}" 873 ) 874 failed_tests.append(register.fname) 875 elif index == 0: 876 message = f"{register.fname} passed reading default value {hex(elem)}" 877 logger.write_result_to_html_report(message) 878 logger.write_info_to_report(message) 879 else: 880 message = f"{register.fname} had value correctly changed to {hex(elem)}" 881 logger.write_result_to_html_report(message) 882 logger.write_info_to_report(message) 883 884 index += 1 885 886 # BatteryMode 887 # TODO: BatteryMode will be updated at later date 888 889 # mask = 0b111001101111 890 # pattern = 0b000000000010 # 000xx00x0010 891 battery_mode = SMBusReg.BATTERY_MODE 892 read_bm_response = _smbus.read_register(battery_mode) 893 # bm_bytes = int.from_bytes(read_bm_response[1], byteorder="little") 894 # logger.write_info_to_report(f"Bytes of {battery_mode.fname}: {bm_bytes}") 895 896 # masked_response = mask & bm_bytes 897 expected_value = 0x0000.to_bytes(2, byteorder="little") 898 if read_bm_response[1] == expected_value: 899 logger.write_info_to_report(f"{battery_mode.fname} passed response check") 900 else: 901 logger.write_warning_to_report(f"{battery_mode.fname} did not have expected result of: 0x0000") 902 failed_tests.append(battery_mode.fname) 903 904 # Overall results report 905 if failed_tests: 906 failed_registers = list(dict.fromkeys(failed_tests)) 907 message = f"{len(failed_registers)} register(s) failed at least one test: {', '.join(failed_registers)}" 908 logger.write_result_to_html_report(f"<font color='#990000'> {message}</font>") 909 pytest.fail(message) 910 else: 911 logger.write_result_to_html_report("All tested registers passed writable test")
SMBus Writable Registers
789 def test_smbus_writable_registers(self) -> None: 790 """ 791 | Description | Validate SMBus Writable Registers | 792 | :------------------- | :--------------------------------------------------------------------- | 793 | GitHub Issue | turnaroundfactor/HITL#397 | 794 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 795jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 796 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 797 | Instructions | 1. Set thermistors to 23C </br>\ 798 2. Put cells in a rested state at 3.7V per cell </br>\ 799 3. Host to Battery Read Word (Register 1): 0x00 </br>\ 800 5. Host to Battery Read Word (Register 2): 0x0438 </br>\ 801 6. Host to Battery Write Word (Register 2): 0xBEEF </br>\ 802 7. Host to Battery Read Word (Register 2): 0xBEEF </br>\ 803 8. Host to Battery Read Word (Register 3): 0x000A </br>\ 804 9. Host to Battery Write Word (Register 3): 0xBEEF </br>\ 805 10. Host to Battery Read Word (Register 3): 0xBEEF </br>\ 806 11. Host to Battery Write Word (Register 3): 0x000A </br>\ 807 12. Host to Battery Read Word (Register 3): 0x000A </br>\ 808 13. Host to Battery Read Word (Register 4): 000xx00x0010 (where x is "don't care") | 809 | Pass / Fail Criteria | ⦁ ManufacturerAccess (Battery Register 1) returns 0x00 </br>\ 810 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0x0438 </br>\ 811 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0xBEEF </br>\ 812 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 813 ⦁ Remaining Time Alarm (Battery Register 3) returns 0xBEEF </br>\ 814 ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A </br>\ 815 ⦁ Battery Mode (Battery Register 4) returns 000xx00x0010 | 816 | Estimated Duration | 30 seconds | 817 | Note | When specified (see 3.1), batteries shall be compliant with System \ 818 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 819 Data (SBData) Specification, version 1.1, with the exception that \ 820 SBData safety signal hardware requirements therein shall be replaced \ 821 with a charge enable when a charge enable is specified (see 3.1 and \ 822 3.5.6). Certification is required. Batteries shall be compatible \ 823 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 824 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 825 accurate within +0/-5% of the actual state of charge for the battery \ 826 under test throughout the discharge. Manufacturer and battery data \ 827 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 828 logic circuitry. Pull-up resistors will be provided by the charger. \ 829 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 830 Smart batteries may act as master or slave on the bus, but must \ 831 perform bus master timing arbitration according to the SMBus \ 832 specification when acting as master. | 833 """ 834 time.sleep(30) 835 836 assert (serial_data := serial_monitor.read()) 837 ten_percent_capacity = float(serial_data["milliamp_hour_capacity"]) // 10 838 default_value = 0x000A 839 easy_spot_value = 0xBEEF 840 smbus_registers = [ 841 {"sm_register": SMBusReg.MANUFACTURING_ACCESS, "data": [0]}, 842 { 843 "sm_register": SMBusReg.REMAINING_CAPACITY_ALARM, 844 "data": [ten_percent_capacity, easy_spot_value], 845 }, 846 { 847 "sm_register": SMBusReg.REMAINING_TIME_ALARM, 848 "data": [default_value, easy_spot_value, default_value], 849 }, 850 ] 851 failed_tests = [] 852 853 # Manufacturing Access, Remaining Capacity Alarm, Remaining Time Alarm 854 for smbus_register in smbus_registers: 855 register = cast(SMBusReg, smbus_register["sm_register"]) 856 index = 0 857 for elem in map(int, cast(list[int], smbus_register["data"])): 858 if index > 0: 859 logger.write_info_to_report(f"Writing {hex(elem)} to {register.fname}") 860 _smbus.write_register(register, elem) 861 862 read_sm_response = _smbus.read_register(register) 863 864 expected_value = elem.to_bytes(2, byteorder="little") 865 if read_sm_response[1] != expected_value: 866 logger.write_result_to_html_report( 867 f"{register.fname} did not have expected result of {hex(elem)}, " 868 f"instead was: {read_sm_response[1]!r}" 869 ) 870 logger.write_warning_to_report( 871 f"{register.fname} did not have expected result of {hex(elem)}, " 872 f"instead was: {read_sm_response[1]!r}" 873 ) 874 failed_tests.append(register.fname) 875 elif index == 0: 876 message = f"{register.fname} passed reading default value {hex(elem)}" 877 logger.write_result_to_html_report(message) 878 logger.write_info_to_report(message) 879 else: 880 message = f"{register.fname} had value correctly changed to {hex(elem)}" 881 logger.write_result_to_html_report(message) 882 logger.write_info_to_report(message) 883 884 index += 1 885 886 # BatteryMode 887 # TODO: BatteryMode will be updated at later date 888 889 # mask = 0b111001101111 890 # pattern = 0b000000000010 # 000xx00x0010 891 battery_mode = SMBusReg.BATTERY_MODE 892 read_bm_response = _smbus.read_register(battery_mode) 893 # bm_bytes = int.from_bytes(read_bm_response[1], byteorder="little") 894 # logger.write_info_to_report(f"Bytes of {battery_mode.fname}: {bm_bytes}") 895 896 # masked_response = mask & bm_bytes 897 expected_value = 0x0000.to_bytes(2, byteorder="little") 898 if read_bm_response[1] == expected_value: 899 logger.write_info_to_report(f"{battery_mode.fname} passed response check") 900 else: 901 logger.write_warning_to_report(f"{battery_mode.fname} did not have expected result of: 0x0000") 902 failed_tests.append(battery_mode.fname) 903 904 # Overall results report 905 if failed_tests: 906 failed_registers = list(dict.fromkeys(failed_tests)) 907 message = f"{len(failed_registers)} register(s) failed at least one test: {', '.join(failed_registers)}" 908 logger.write_result_to_html_report(f"<font color='#990000'> {message}</font>") 909 pytest.fail(message) 910 else: 911 logger.write_result_to_html_report("All tested registers passed writable test")
| Description | Validate SMBus Writable Registers |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#397 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.9.1 (SMBus) |
| Instructions | 1. Set thermistors to 23C 2. Put cells in a rested state at 3.7V per cell 3. Host to Battery Read Word (Register 1): 0x00 5. Host to Battery Read Word (Register 2): 0x0438 6. Host to Battery Write Word (Register 2): 0xBEEF 7. Host to Battery Read Word (Register 2): 0xBEEF 8. Host to Battery Read Word (Register 3): 0x000A 9. Host to Battery Write Word (Register 3): 0xBEEF 10. Host to Battery Read Word (Register 3): 0xBEEF 11. Host to Battery Write Word (Register 3): 0x000A 12. Host to Battery Read Word (Register 3): 0x000A 13. Host to Battery Read Word (Register 4): 000xx00x0010 (where x is "don't care") |
| Pass / Fail Criteria | ⦁ ManufacturerAccess (Battery Register 1) returns 0x00 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0x0438 ⦁ Remaining Capacity Alarm (Battery Register 2) returns 0xBEEF ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A ⦁ Remaining Time Alarm (Battery Register 3) returns 0xBEEF ⦁ Remaining Time Alarm (Battery Register 3) returns 0x000A ⦁ Battery Mode (Battery Register 4) returns 000xx00x0010 |
| Estimated Duration | 30 seconds |
| Note | When specified (see 3.1), batteries shall be compliant with System Management Bus (SMBus) Specification Revision 1.1 and Smart Battery Data (SBData) Specification, version 1.1, with the exception that SBData safety signal hardware requirements therein shall be replaced with a charge enable when a charge enable is specified (see 3.1 and 3.5.6). Certification is required. Batteries shall be compatible with appropriate Level 2 and Level 3 chargers (see 6.4.7). When tested as specified in 4.7.2.15.1, SMBus data output shall be accurate within +0/-5% of the actual state of charge for the battery under test throughout the discharge. Manufacturer and battery data shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V logic circuitry. Pull-up resistors will be provided by the charger. SMBus circuitry shall respond to a SMBus query within 2 seconds. Smart batteries may act as master or slave on the bus, but must perform bus master timing arbitration according to the SMBus specification when acting as master. |
914def analyze_register_response( 915 requirements: list[dict[str, SMBusReg | float] | dict[str, SMBusReg | int] | dict[str, SMBusReg | bool]], 916 failed_list: list[str], 917 at_rate: int, 918) -> list[str]: 919 """Reads SMBus register response and validates""" 920 921 full_amount = 0 922 remaining_amount = 0 923 924 for elem in requirements: 925 register: SMBusReg = cast(SMBusReg, elem["register"]) 926 requirement = elem["requirement"] 927 928 read_response = _smbus.read_register(register) 929 if requirement is True: 930 if not read_response[0]: 931 message = f"Invalid response for {register.fname}. Expected non-zero value, but got {read_response[0]}" 932 logger.write_warning_to_report(message) 933 failed_list.append(f"{register.fname} after setting AtRate value to {at_rate}(mA)") 934 message = f"Received valid response for {register.fname}." 935 logger.write_info_to_report(message) 936 937 else: 938 assert isinstance(read_response[0], int | float) and isinstance(requirement, int | float) 939 if not math.isclose(read_response[0], requirement, rel_tol=0.1): 940 message = f"Invalid response for {register.fname}. Expected {requirement}, but got {read_response[0]}" 941 logger.write_warning_to_report(message) 942 failed_list.append(f"{register.fname} after setting AtRate value to {at_rate}(mA)") 943 944 message = f"Received valid response for {register.fname}" 945 logger.write_info_to_report(message) 946 947 if register == SMBusReg.AT_RATE_TIME_TO_FULL: 948 assert isinstance(read_response[0], int) 949 full_amount = read_response[0] 950 951 if register == SMBusReg.AT_RATE_TIME_TO_EMPTY: 952 assert isinstance(read_response[0], int) 953 remaining_amount = read_response[0] 954 955 logger.write_result_to_html_report(f"{at_rate} AtRate, {remaining_amount} Remaining, {full_amount} Full") 956 return failed_list
Reads SMBus register response and validates
960class TestSMBusAtRate: 961 """Validate the SMBus AtRate Commands""" 962 963 def test_smbus_at_rate(self): 964 # TODO: Update Documentation 965 """ 966 | Description | Validate SMBus AtRate Commands | 967 | :------------------- | :--------------------------------------------------------------------- | 968 | GitHub Issue | turnaroundfactor/HITL#398 | 969 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 970jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 971 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 972 | Instructions | 1. Write word 1000 (unit is mA) to AtRate[Register 0x4] </br>\ 973 2. Read word from AtRateTimeToFull[0x5] </br>\ 974 3. Read word from AtRate TimeToEmpty[0x6] </br>\ 975 4. Read word from AtRateOK[0x7] </br>\ 976 5. Write word -1000 to AtRate[Register 0x4] </br>\ 977 6. Read word from AtRateTimeToFull[0x5] </br>\ 978 7. Read word from AtRateTimeToEmpty[0x6] </br>\ 979 8. Charge battery at 2A </br>\ 980 9. Read word from AtRateOK[0x7] </br>\ 981 10. Charge battery at 0.1A </br>\ 982 11. Read word from AtRateOK[0x7] </br>\ 983 12. Discharge battery at 2A </br>\ 984 13. Read word from AtRateOK[0x7] </br>\ 985 14. Discharge battery at 0.1A </br>\ 986 15. Read word from AtRateOK[0x7] </br>\ 987 16. Write word 0 to AtRate[Register 0x4] </br>\ 988 17. Read word from AtRateTimeToFull[0x6] </br>\ 989 18. Read word from AtRateTimeToEmpty[0x6] </br>\ 990 19. Read word from AtRateOK[0x7] </br>\ 991 20. Write word 0x8000 to BatteryMode[Register 0x3] </br>\ 992 21. Write word 0x0808 to BatteryMode[Register 0x3] | 993 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 994 ⦁ Expect AtRateTimeToFull to be FullChargeCapacity-RemainingCapacity/1000/60 </br>\ 995 ⦁ Expect AtRateTimeToEmpty to be 65,635 </br>\ 996 ⦁ Expect AtRateOK to be True (non-zero) </br>\ 997 Discharging(ma) ---- </br>\ 998 ⦁ Expect AtRateTimeToFull to be 65,535 </br>\ 999 ⦁ Expect AtRateTimeToEmpty to be RemainingCapacity / 1000 / 60 </br>\ 1000 ⦁ Expect AtRateOK to be True (non-zero) for all charge changes </br>\ 1001 Rest (mA) ---- </br>\ 1002 ⦁ Expect AtRAteTimeToFull to be 65,535 </br>\ 1003 ⦁ Expect AtRateTimeToEmpty to be 65,535 </br>\ 1004 ⦁ Expect AtRateOK to be True (non-zero) | 1005 | Estimated Duration | 10 seconds | 1006 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1007 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1008 Data (SBData) Specification, version 1.1, with the exception that \ 1009 SBData safety signal hardware requirements therein shall be replaced \ 1010 with a charge enable when a charge enable is specified (see 3.1 and \ 1011 3.5.6). Certification is required. Batteries shall be compatible \ 1012 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1013 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1014 accurate within +0/-5% of the actual state of charge for the battery \ 1015 under test throughout the discharge. Manufacturer and battery data \ 1016 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1017 logic circuitry. Pull-up resistors will be provided by the charger. \ 1018 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1019 Smart batteries may act as master or slave on the bus, but must \ 1020 perform bus master timing arbitration according to the SMBus \ 1021 specification when acting as master. | 1022 """ 1023 time.sleep(30) 1024 1025 at_rate_register = SMBusReg.AT_RATE 1026 at_rate_time_to_full_register = SMBusReg.AT_RATE_TIME_TO_FULL 1027 at_rate_time_to_empty_register = SMBusReg.AT_RATE_TIME_TO_EMPTY 1028 at_rate_ok_register = SMBusReg.AT_RATE_OK 1029 # 1030 failed_tests = [] 1031 1032 # 1033 # # Charging(mA) 1034 logger.write_info_to_report("Setting AtRate value to 1000(mA)") 1035 _smbus.write_register(at_rate_register, 1000) 1036 time.sleep(2) 1037 at_rate_response = _smbus.read_register(at_rate_register) 1038 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1039 1040 full_charge = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1041 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1042 calculated_time_to_full = (full_charge[0] - remaining_capacity[0]) / 1000 * 60 / 0.975 1043 1044 charging_elems = [ 1045 {"register": at_rate_time_to_full_register, "requirement": calculated_time_to_full}, 1046 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1047 {"register": at_rate_ok_register, "requirement": True}, 1048 ] 1049 1050 failed_tests = analyze_register_response(charging_elems, failed_tests, at_rate_response[0]) 1051 1052 # # Discharging 1053 logger.write_info_to_report("Setting AtRate value to -1000(mA)") 1054 _smbus.write_register(at_rate_register, -1000) 1055 time.sleep(2) 1056 at_rate_response = _smbus.read_register(at_rate_register) 1057 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1058 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1059 empty_requirement = remaining_capacity[0] / 1000 * 60 * 0.975 1060 1061 discharging_elems = [ 1062 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1063 {"register": at_rate_time_to_empty_register, "requirement": empty_requirement}, 1064 ] 1065 1066 failed_tests = analyze_register_response(discharging_elems, failed_tests, at_rate_response[0]) 1067 1068 # AtRateOK -- Charging & Discharging 1069 rates = [ 1070 {"charge": True, "rate": 2}, 1071 {"charge": True, "rate": 0.1}, 1072 {"charge": False, "rate": 2}, 1073 {"charge": False, "rate": 0.1}, 1074 ] 1075 1076 for elem in rates: 1077 if elem["charge"]: 1078 logger.write_info_to_report(f"Charging battery at {elem['rate']}A") 1079 with _bms.charger(16.8, elem["rate"]): 1080 time.sleep(2) 1081 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1082 if not at_rate_ok_response[0]: 1083 message = ( 1084 f"Invalid response for {at_rate_ok_register.fname}. " 1085 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1086 ) 1087 logger.write_warning_to_report(message) 1088 failed_tests.append( 1089 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1090 ) 1091 else: 1092 logger.write_result_to_html_report( 1093 f"{at_rate_ok_register.fname} had expected value of True after charging at {elem['rate']}A" 1094 ) 1095 else: 1096 logger.write_info_to_report(f"Discharging battery at {elem['rate']}A") 1097 with _bms.load(elem["rate"]): 1098 time.sleep(2) 1099 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1100 if not at_rate_ok_response[0]: 1101 message = ( 1102 f"Invalid response for {at_rate_ok_register.fname}. " 1103 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1104 ) 1105 logger.write_warning_to_report(message) 1106 failed_tests.append( 1107 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1108 ) 1109 else: 1110 logger.write_result_to_html_report( 1111 f"{at_rate_ok_register.fname} had expected value of True " 1112 f"after discharging at {elem['rate']}A" 1113 ) 1114 # Rest(mA) 1115 logger.write_info_to_report("Setting AtRate value to 0(mA)") 1116 _smbus.write_register(at_rate_register, 0) 1117 time.sleep(2) 1118 1119 at_rate_response = _smbus.read_register(at_rate_register) 1120 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1121 full_charge_three = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1122 logger.write_info_to_report(f"{SMBusReg.FULL_CHARGE_CAPACITY.fname}: {full_charge_three}") 1123 1124 remaining_capacity_three = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1125 logger.write_info_to_report(f"{SMBusReg.REMAINING_CAPACITY.fname}: {remaining_capacity_three}") 1126 resting_elems = [ 1127 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1128 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1129 {"register": at_rate_ok_register, "requirement": True}, 1130 ] 1131 1132 failed_tests = analyze_register_response(resting_elems, failed_tests, at_rate_response[0]) 1133 1134 # Power Mode 1135 battery_mode_register = SMBusReg.BATTERY_MODE 1136 writing_value = 0x8000 1137 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1138 _smbus.write_register(battery_mode_register, writing_value) 1139 batt_response = _smbus.read_register(battery_mode_register) 1140 logger.write_info_to_report(f"{battery_mode_register.fname} after writing {writing_value}: {batt_response}") 1141 batt_bytes = batt_response[1] 1142 batt_analyzed = BatteryMode(batt_bytes) 1143 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1144 1145 writing_value = 0x8080 1146 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1147 _smbus.write_register(battery_mode_register, writing_value) 1148 1149 batt_mode_response = _smbus.read_register(battery_mode_register) 1150 logger.write_info_to_report( 1151 f"{battery_mode_register.fname} after writing {writing_value}: {batt_mode_response}" 1152 ) 1153 batt_bytes = batt_mode_response[1] 1154 batt_analyzed = BatteryMode(batt_bytes) 1155 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1156 if batt_analyzed.capacity_mode is not True: 1157 logger.write_warning_to_report( 1158 f"{battery_mode_register.fname} did not have Capacity Mode with expected " 1159 f"value of True, instead received {batt_analyzed.capacity_mode}" 1160 ) 1161 failed_tests.append(f"{battery_mode_register.fname} did not have expected Capacity Mode value") 1162 else: 1163 logger.write_result_to_html_report( 1164 f"{battery_mode_register.fname} had Capacity Mode with expected value of {batt_analyzed.capacity_mode}" 1165 ) 1166 1167 if failed_tests: 1168 message = f"{len(failed_tests)} AtRate tests failed: {', '.join(failed_tests)}" 1169 logger.write_warning_to_report(message) 1170 pytest.fail(f"<font color='#990000'>{message}</font>") 1171 1172 logger.write_result_to_html_report("All AtRate SMBus tests passed")
Validate the SMBus AtRate Commands
963 def test_smbus_at_rate(self): 964 # TODO: Update Documentation 965 """ 966 | Description | Validate SMBus AtRate Commands | 967 | :------------------- | :--------------------------------------------------------------------- | 968 | GitHub Issue | turnaroundfactor/HITL#398 | 969 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 970jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 971 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 972 | Instructions | 1. Write word 1000 (unit is mA) to AtRate[Register 0x4] </br>\ 973 2. Read word from AtRateTimeToFull[0x5] </br>\ 974 3. Read word from AtRate TimeToEmpty[0x6] </br>\ 975 4. Read word from AtRateOK[0x7] </br>\ 976 5. Write word -1000 to AtRate[Register 0x4] </br>\ 977 6. Read word from AtRateTimeToFull[0x5] </br>\ 978 7. Read word from AtRateTimeToEmpty[0x6] </br>\ 979 8. Charge battery at 2A </br>\ 980 9. Read word from AtRateOK[0x7] </br>\ 981 10. Charge battery at 0.1A </br>\ 982 11. Read word from AtRateOK[0x7] </br>\ 983 12. Discharge battery at 2A </br>\ 984 13. Read word from AtRateOK[0x7] </br>\ 985 14. Discharge battery at 0.1A </br>\ 986 15. Read word from AtRateOK[0x7] </br>\ 987 16. Write word 0 to AtRate[Register 0x4] </br>\ 988 17. Read word from AtRateTimeToFull[0x6] </br>\ 989 18. Read word from AtRateTimeToEmpty[0x6] </br>\ 990 19. Read word from AtRateOK[0x7] </br>\ 991 20. Write word 0x8000 to BatteryMode[Register 0x3] </br>\ 992 21. Write word 0x0808 to BatteryMode[Register 0x3] | 993 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 994 ⦁ Expect AtRateTimeToFull to be FullChargeCapacity-RemainingCapacity/1000/60 </br>\ 995 ⦁ Expect AtRateTimeToEmpty to be 65,635 </br>\ 996 ⦁ Expect AtRateOK to be True (non-zero) </br>\ 997 Discharging(ma) ---- </br>\ 998 ⦁ Expect AtRateTimeToFull to be 65,535 </br>\ 999 ⦁ Expect AtRateTimeToEmpty to be RemainingCapacity / 1000 / 60 </br>\ 1000 ⦁ Expect AtRateOK to be True (non-zero) for all charge changes </br>\ 1001 Rest (mA) ---- </br>\ 1002 ⦁ Expect AtRAteTimeToFull to be 65,535 </br>\ 1003 ⦁ Expect AtRateTimeToEmpty to be 65,535 </br>\ 1004 ⦁ Expect AtRateOK to be True (non-zero) | 1005 | Estimated Duration | 10 seconds | 1006 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1007 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1008 Data (SBData) Specification, version 1.1, with the exception that \ 1009 SBData safety signal hardware requirements therein shall be replaced \ 1010 with a charge enable when a charge enable is specified (see 3.1 and \ 1011 3.5.6). Certification is required. Batteries shall be compatible \ 1012 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1013 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1014 accurate within +0/-5% of the actual state of charge for the battery \ 1015 under test throughout the discharge. Manufacturer and battery data \ 1016 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1017 logic circuitry. Pull-up resistors will be provided by the charger. \ 1018 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1019 Smart batteries may act as master or slave on the bus, but must \ 1020 perform bus master timing arbitration according to the SMBus \ 1021 specification when acting as master. | 1022 """ 1023 time.sleep(30) 1024 1025 at_rate_register = SMBusReg.AT_RATE 1026 at_rate_time_to_full_register = SMBusReg.AT_RATE_TIME_TO_FULL 1027 at_rate_time_to_empty_register = SMBusReg.AT_RATE_TIME_TO_EMPTY 1028 at_rate_ok_register = SMBusReg.AT_RATE_OK 1029 # 1030 failed_tests = [] 1031 1032 # 1033 # # Charging(mA) 1034 logger.write_info_to_report("Setting AtRate value to 1000(mA)") 1035 _smbus.write_register(at_rate_register, 1000) 1036 time.sleep(2) 1037 at_rate_response = _smbus.read_register(at_rate_register) 1038 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1039 1040 full_charge = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1041 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1042 calculated_time_to_full = (full_charge[0] - remaining_capacity[0]) / 1000 * 60 / 0.975 1043 1044 charging_elems = [ 1045 {"register": at_rate_time_to_full_register, "requirement": calculated_time_to_full}, 1046 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1047 {"register": at_rate_ok_register, "requirement": True}, 1048 ] 1049 1050 failed_tests = analyze_register_response(charging_elems, failed_tests, at_rate_response[0]) 1051 1052 # # Discharging 1053 logger.write_info_to_report("Setting AtRate value to -1000(mA)") 1054 _smbus.write_register(at_rate_register, -1000) 1055 time.sleep(2) 1056 at_rate_response = _smbus.read_register(at_rate_register) 1057 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1058 remaining_capacity = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1059 empty_requirement = remaining_capacity[0] / 1000 * 60 * 0.975 1060 1061 discharging_elems = [ 1062 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1063 {"register": at_rate_time_to_empty_register, "requirement": empty_requirement}, 1064 ] 1065 1066 failed_tests = analyze_register_response(discharging_elems, failed_tests, at_rate_response[0]) 1067 1068 # AtRateOK -- Charging & Discharging 1069 rates = [ 1070 {"charge": True, "rate": 2}, 1071 {"charge": True, "rate": 0.1}, 1072 {"charge": False, "rate": 2}, 1073 {"charge": False, "rate": 0.1}, 1074 ] 1075 1076 for elem in rates: 1077 if elem["charge"]: 1078 logger.write_info_to_report(f"Charging battery at {elem['rate']}A") 1079 with _bms.charger(16.8, elem["rate"]): 1080 time.sleep(2) 1081 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1082 if not at_rate_ok_response[0]: 1083 message = ( 1084 f"Invalid response for {at_rate_ok_register.fname}. " 1085 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1086 ) 1087 logger.write_warning_to_report(message) 1088 failed_tests.append( 1089 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1090 ) 1091 else: 1092 logger.write_result_to_html_report( 1093 f"{at_rate_ok_register.fname} had expected value of True after charging at {elem['rate']}A" 1094 ) 1095 else: 1096 logger.write_info_to_report(f"Discharging battery at {elem['rate']}A") 1097 with _bms.load(elem["rate"]): 1098 time.sleep(2) 1099 at_rate_ok_response = _smbus.read_register(at_rate_ok_register) 1100 if not at_rate_ok_response[0]: 1101 message = ( 1102 f"Invalid response for {at_rate_ok_register.fname}. " 1103 f"Expected non-zero value, but got {at_rate_ok_response[0]}" 1104 ) 1105 logger.write_warning_to_report(message) 1106 failed_tests.append( 1107 f"{at_rate_ok_register.fname} after setting AtRate value to {at_rate_response[0]}(mA)" 1108 ) 1109 else: 1110 logger.write_result_to_html_report( 1111 f"{at_rate_ok_register.fname} had expected value of True " 1112 f"after discharging at {elem['rate']}A" 1113 ) 1114 # Rest(mA) 1115 logger.write_info_to_report("Setting AtRate value to 0(mA)") 1116 _smbus.write_register(at_rate_register, 0) 1117 time.sleep(2) 1118 1119 at_rate_response = _smbus.read_register(at_rate_register) 1120 logger.write_info_to_report(f"AtRate is now: {at_rate_response}") 1121 full_charge_three = _smbus.read_register(SMBusReg.FULL_CHARGE_CAPACITY) 1122 logger.write_info_to_report(f"{SMBusReg.FULL_CHARGE_CAPACITY.fname}: {full_charge_three}") 1123 1124 remaining_capacity_three = _smbus.read_register(SMBusReg.REMAINING_CAPACITY) 1125 logger.write_info_to_report(f"{SMBusReg.REMAINING_CAPACITY.fname}: {remaining_capacity_three}") 1126 resting_elems = [ 1127 {"register": at_rate_time_to_full_register, "requirement": 65535}, 1128 {"register": at_rate_time_to_empty_register, "requirement": 65535}, 1129 {"register": at_rate_ok_register, "requirement": True}, 1130 ] 1131 1132 failed_tests = analyze_register_response(resting_elems, failed_tests, at_rate_response[0]) 1133 1134 # Power Mode 1135 battery_mode_register = SMBusReg.BATTERY_MODE 1136 writing_value = 0x8000 1137 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1138 _smbus.write_register(battery_mode_register, writing_value) 1139 batt_response = _smbus.read_register(battery_mode_register) 1140 logger.write_info_to_report(f"{battery_mode_register.fname} after writing {writing_value}: {batt_response}") 1141 batt_bytes = batt_response[1] 1142 batt_analyzed = BatteryMode(batt_bytes) 1143 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1144 1145 writing_value = 0x8080 1146 print(f"Writing {writing_value} to {battery_mode_register.fname}") 1147 _smbus.write_register(battery_mode_register, writing_value) 1148 1149 batt_mode_response = _smbus.read_register(battery_mode_register) 1150 logger.write_info_to_report( 1151 f"{battery_mode_register.fname} after writing {writing_value}: {batt_mode_response}" 1152 ) 1153 batt_bytes = batt_mode_response[1] 1154 batt_analyzed = BatteryMode(batt_bytes) 1155 logger.write_info_to_report(f"Capacity Mode after writing {writing_value}: {batt_analyzed.capacity_mode}") 1156 if batt_analyzed.capacity_mode is not True: 1157 logger.write_warning_to_report( 1158 f"{battery_mode_register.fname} did not have Capacity Mode with expected " 1159 f"value of True, instead received {batt_analyzed.capacity_mode}" 1160 ) 1161 failed_tests.append(f"{battery_mode_register.fname} did not have expected Capacity Mode value") 1162 else: 1163 logger.write_result_to_html_report( 1164 f"{battery_mode_register.fname} had Capacity Mode with expected value of {batt_analyzed.capacity_mode}" 1165 ) 1166 1167 if failed_tests: 1168 message = f"{len(failed_tests)} AtRate tests failed: {', '.join(failed_tests)}" 1169 logger.write_warning_to_report(message) 1170 pytest.fail(f"<font color='#990000'>{message}</font>") 1171 1172 logger.write_result_to_html_report("All AtRate SMBus tests passed")
| Description | Validate SMBus AtRate Commands |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#398 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.9.1 (SMBus) |
| Instructions | 1. Write word 1000 (unit is mA) to AtRate[Register 0x4] 2. Read word from AtRateTimeToFull[0x5] 3. Read word from AtRate TimeToEmpty[0x6] 4. Read word from AtRateOK[0x7] 5. Write word -1000 to AtRate[Register 0x4] 6. Read word from AtRateTimeToFull[0x5] 7. Read word from AtRateTimeToEmpty[0x6] 8. Charge battery at 2A 9. Read word from AtRateOK[0x7] 10. Charge battery at 0.1A 11. Read word from AtRateOK[0x7] 12. Discharge battery at 2A 13. Read word from AtRateOK[0x7] 14. Discharge battery at 0.1A 15. Read word from AtRateOK[0x7] 16. Write word 0 to AtRate[Register 0x4] 17. Read word from AtRateTimeToFull[0x6] 18. Read word from AtRateTimeToEmpty[0x6] 19. Read word from AtRateOK[0x7] 20. Write word 0x8000 to BatteryMode[Register 0x3] 21. Write word 0x0808 to BatteryMode[Register 0x3] |
| Pass / Fail Criteria | Charging (mA) ---- ⦁ Expect AtRateTimeToFull to be FullChargeCapacity-RemainingCapacity/1000/60 ⦁ Expect AtRateTimeToEmpty to be 65,635 ⦁ Expect AtRateOK to be True (non-zero) Discharging(ma) ---- ⦁ Expect AtRateTimeToFull to be 65,535 ⦁ Expect AtRateTimeToEmpty to be RemainingCapacity / 1000 / 60 ⦁ Expect AtRateOK to be True (non-zero) for all charge changes Rest (mA) ---- ⦁ Expect AtRAteTimeToFull to be 65,535 ⦁ Expect AtRateTimeToEmpty to be 65,535 ⦁ Expect AtRateOK to be True (non-zero) |
| Estimated Duration | 10 seconds |
| Note | When specified (see 3.1), batteries shall be compliant with System Management Bus (SMBus) Specification Revision 1.1 and Smart Battery Data (SBData) Specification, version 1.1, with the exception that SBData safety signal hardware requirements therein shall be replaced with a charge enable when a charge enable is specified (see 3.1 and 3.5.6). Certification is required. Batteries shall be compatible with appropriate Level 2 and Level 3 chargers (see 6.4.7). When tested as specified in 4.7.2.15.1, SMBus data output shall be accurate within +0/-5% of the actual state of charge for the battery under test throughout the discharge. Manufacturer and battery data shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V logic circuitry. Pull-up resistors will be provided by the charger. SMBus circuitry shall respond to a SMBus query within 2 seconds. Smart batteries may act as master or slave on the bus, but must perform bus master timing arbitration according to the SMBus specification when acting as master. |
1175@pytest.mark.parametrize("reset_test_environment", [{"soc": 0.50}], indirect=True) 1176class TestConstantSMBusValues: 1177 """Test constant SMBus values""" 1178 1179 def test_constant_smbus_values(self): 1180 """ 1181 | Description | Constant SMBus Values | 1182 | :------------------- | :--------------------------------------------------------------------- | 1183 | GitHub Issue | turnaroundfactor/HITL#385 | 1184 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1185 jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1186 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1187 | Instructions | 1. Read Device Chemistry [0x22] </br>\ 1188 2. Read Device Name [0x21] </br>\ 1189 3. Read Manufacturer Name [0x20] </br>\ 1190 4. Read Serial Number [0x1C] </br>\ 1191 5. Read Manufacturer Date [0x1B] </br>\ 1192 6. Read Specification Info [0x1A] </br>\ 1193 7. Read Design Voltage [0x19] </br>\ 1194 8. Read Design Capacity [0x18] </br>\ 1195 9. Read Charging Voltage [0x15] </br>\ 1196 10. Read Charging Current [0x14] </br>\ 1197 11. Read Manufacturer Access [0x00] </br>\ 1198 12. Read Remaining Time Alarm [0x02] </br>\ 1199 13. Read Remaining Capacity Alarm [0x01] </br>\ 1200 14. Read AtRateOk [0x07] </br>\ 1201 15. Read AtRateTimeToEmpty [0x06] </br>\ 1202 16. Read AtRateTimeToFull [0x05] </br>\ 1203 17. Read AtRate [0x04] </br>\ 1204 18. Read Battery Mode [0x03] </br>\ 1205 19. Read Max Error [0x0C] </br>\ 1206 20. Read Full Charge Capacity [0x10] | 1207 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1208 ⦁ Expect Device Chemistry [0x22] to be LION </br>\ 1209 ⦁ Expect Device Name [0x21] to be BB-2590/U </br>\ 1210 ⦁ Expect Manufacturer Name [0x20] to be TURN-AROUND FACTOR </br>\ 1211 ⦁ Expect Serial Number [0x1C] to be a unique value </br>\ 1212 ⦁ Expect Manufacturer Date [0x1B] to be 0x0100 </br>\ 1213 ⦁ Expect Specification Info [0x1A] to be 0x0100 </br>\ 1214 ⦁ Expect Design Voltage [0x19] to be 16800 </br>\ 1215 ⦁ Expect Design Capacity [0x18] to be CAPACITY * 0.975 </br>\ 1216 ⦁ Expect Charging Voltage [0x15] to be 16800 </br>\ 1217 ⦁ Expect Charging Current [0x14] to be 2000 </br>\ 1218 ⦁ Expect Manufacturer Access [0x00] to be 0 </br>\ 1219 ⦁ Expect Remaining Time Alarm [0x02] to be 10 </br>\ 1220 ⦁ Expect Remaining Capacity Alarm [0x01] to be SOC * Design Capacity </br>\ 1221 ⦁ Expect AtRateOk [0x07] to be True </br>\ 1222 ⦁ Expect AtRateTimeToEmpty [0x06] to be 65535 </br>\ 1223 ⦁ Expect AtRateTimeToFull [0x05] to be 65535 </br>\ 1224 ⦁ Expect AtRate [0x04] to be 0 </br>\ 1225 ⦁ Expect Battery Mode [0x03] to be 0 </br>\ 1226 ⦁ Expect Max Error [0x0C] to be 0 </br>\ 1227 ⦁ Expect Full Charge Capacity [0x10] to be CAPACITY | 1228 | Estimated Duration | 10 seconds | 1229 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1230 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1231 Data (SBData) Specification, version 1.1, with the exception that \ 1232 SBData safety signal hardware requirements therein shall be replaced \ 1233 with a charge enable when a charge enable is specified (see 3.1 and \ 1234 3.5.6). Certification is required. Batteries shall be compatible \ 1235 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1236 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1237 accurate within +0/-5% of the actual state of charge for the battery \ 1238 under test throughout the discharge. Manufacturer and battery data \ 1239 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1240 logic circuitry. Pull-up resistors will be provided by the charger. \ 1241 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1242 Smart batteries may act as master or slave on the bus, but must \ 1243 perform bus master timing arbitration according to the SMBus \ 1244 specification when acting as master. | 1245 """ 1246 constant_elements = [ 1247 {"register": SMBusReg.DEVICE_CHEMISTRY, "requirement": "LION"}, 1248 {"register": SMBusReg.DEVICE_NAME, "requirement": "BB-2590/U"}, 1249 {"register": SMBusReg.MANUFACTURER_NAME, "requirement": "TURN-AROUND FACTOR"}, 1250 {"register": SMBusReg.SERIAL_NUM, "requirement": 0xFFFF}, 1251 {"register": SMBusReg.MANUFACTURER_DATE, "requirement": None}, 1252 {"register": SMBusReg.SPECIFICATION_INFO, "requirement": None}, 1253 {"register": SMBusReg.DESIGN_VOLTAGE, "requirement": 16800}, 1254 { 1255 "register": SMBusReg.DESIGN_CAPACITY, 1256 "requirement": math.floor(0.975 * CELL_CAPACITY_AH * 1000), 1257 }, 1258 {"register": SMBusReg.CHARGING_VOLTAGE, "requirement": 16800}, 1259 {"register": SMBusReg.CHARGING_CURRENT, "requirement": 2000}, 1260 {"register": SMBusReg.MANUFACTURING_ACCESS, "requirement": 0}, 1261 {"register": SMBusReg.REMAINING_TIME_ALARM, "requirement": 10}, 1262 {"register": SMBusReg.REMAINING_CAPACITY, "requirement": 48}, 1263 {"register": SMBusReg.AT_RATE_OK, "requirement": True}, 1264 {"register": SMBusReg.AT_RATE_TIME_TO_EMPTY, "requirement": 65535}, 1265 {"register": SMBusReg.AT_RATE_TIME_TO_FULL, "requirement": 65535}, 1266 {"register": SMBusReg.AT_RATE, "requirement": 0}, 1267 {"register": SMBusReg.BATTERY_MODE, "requirement": 0}, 1268 {"register": SMBusReg.MAX_ERROR, "requirement": 0}, 1269 { 1270 "register": SMBusReg.FULL_CHARGE_CAPACITY, 1271 "requirement": CELL_CAPACITY_AH * 1000, 1272 }, 1273 ] 1274 test_failed = False 1275 1276 for element in constant_elements: 1277 register = element["register"] 1278 requirement = element["requirement"] 1279 1280 read_response = _smbus.read_register(register) 1281 1282 if register == SMBusReg.SERIAL_NUM: 1283 if read_response[0] != requirement: 1284 logger.write_result_to_html_report( 1285 f"{register.fname} had unique value of {read_response[0]} ({read_response[1]})" 1286 ) 1287 else: 1288 logger.write_result_to_html_report( 1289 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1290 f"not a unique value</font>" 1291 ) 1292 test_failed = True 1293 continue 1294 1295 if register == SMBusReg.REMAINING_CAPACITY: 1296 design_capacity = _smbus.read_register(SMBusReg.DESIGN_CAPACITY) 1297 requirement = design_capacity[0] // 2 1298 if requirement - 3 <= read_response[0] <= requirement + 3: 1299 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1300 else: 1301 logger.write_result_to_html_report( 1302 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1303 f"not {requirement}</font>" 1304 ) 1305 test_failed = True 1306 1307 elif isinstance(requirement, int): 1308 if read_response[0] == requirement or read_response[1] == requirement.to_bytes(2, byteorder="little"): 1309 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1310 else: 1311 logger.write_result_to_html_report( 1312 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1313 f"not {requirement}</font>" 1314 ) 1315 test_failed = True 1316 elif read_response[0] == requirement: 1317 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1318 else: 1319 logger.write_result_to_html_report( 1320 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1321 f"not {requirement}</font>" 1322 ) 1323 test_failed = True 1324 1325 if test_failed: 1326 pytest.fail() 1327 1328 logger.write_result_to_html_report("All SMBus Constant Values passed")
Test constant SMBus values
1179 def test_constant_smbus_values(self): 1180 """ 1181 | Description | Constant SMBus Values | 1182 | :------------------- | :--------------------------------------------------------------------- | 1183 | GitHub Issue | turnaroundfactor/HITL#385 | 1184 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1185 jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1186 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1187 | Instructions | 1. Read Device Chemistry [0x22] </br>\ 1188 2. Read Device Name [0x21] </br>\ 1189 3. Read Manufacturer Name [0x20] </br>\ 1190 4. Read Serial Number [0x1C] </br>\ 1191 5. Read Manufacturer Date [0x1B] </br>\ 1192 6. Read Specification Info [0x1A] </br>\ 1193 7. Read Design Voltage [0x19] </br>\ 1194 8. Read Design Capacity [0x18] </br>\ 1195 9. Read Charging Voltage [0x15] </br>\ 1196 10. Read Charging Current [0x14] </br>\ 1197 11. Read Manufacturer Access [0x00] </br>\ 1198 12. Read Remaining Time Alarm [0x02] </br>\ 1199 13. Read Remaining Capacity Alarm [0x01] </br>\ 1200 14. Read AtRateOk [0x07] </br>\ 1201 15. Read AtRateTimeToEmpty [0x06] </br>\ 1202 16. Read AtRateTimeToFull [0x05] </br>\ 1203 17. Read AtRate [0x04] </br>\ 1204 18. Read Battery Mode [0x03] </br>\ 1205 19. Read Max Error [0x0C] </br>\ 1206 20. Read Full Charge Capacity [0x10] | 1207 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1208 ⦁ Expect Device Chemistry [0x22] to be LION </br>\ 1209 ⦁ Expect Device Name [0x21] to be BB-2590/U </br>\ 1210 ⦁ Expect Manufacturer Name [0x20] to be TURN-AROUND FACTOR </br>\ 1211 ⦁ Expect Serial Number [0x1C] to be a unique value </br>\ 1212 ⦁ Expect Manufacturer Date [0x1B] to be 0x0100 </br>\ 1213 ⦁ Expect Specification Info [0x1A] to be 0x0100 </br>\ 1214 ⦁ Expect Design Voltage [0x19] to be 16800 </br>\ 1215 ⦁ Expect Design Capacity [0x18] to be CAPACITY * 0.975 </br>\ 1216 ⦁ Expect Charging Voltage [0x15] to be 16800 </br>\ 1217 ⦁ Expect Charging Current [0x14] to be 2000 </br>\ 1218 ⦁ Expect Manufacturer Access [0x00] to be 0 </br>\ 1219 ⦁ Expect Remaining Time Alarm [0x02] to be 10 </br>\ 1220 ⦁ Expect Remaining Capacity Alarm [0x01] to be SOC * Design Capacity </br>\ 1221 ⦁ Expect AtRateOk [0x07] to be True </br>\ 1222 ⦁ Expect AtRateTimeToEmpty [0x06] to be 65535 </br>\ 1223 ⦁ Expect AtRateTimeToFull [0x05] to be 65535 </br>\ 1224 ⦁ Expect AtRate [0x04] to be 0 </br>\ 1225 ⦁ Expect Battery Mode [0x03] to be 0 </br>\ 1226 ⦁ Expect Max Error [0x0C] to be 0 </br>\ 1227 ⦁ Expect Full Charge Capacity [0x10] to be CAPACITY | 1228 | Estimated Duration | 10 seconds | 1229 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1230 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1231 Data (SBData) Specification, version 1.1, with the exception that \ 1232 SBData safety signal hardware requirements therein shall be replaced \ 1233 with a charge enable when a charge enable is specified (see 3.1 and \ 1234 3.5.6). Certification is required. Batteries shall be compatible \ 1235 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1236 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1237 accurate within +0/-5% of the actual state of charge for the battery \ 1238 under test throughout the discharge. Manufacturer and battery data \ 1239 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1240 logic circuitry. Pull-up resistors will be provided by the charger. \ 1241 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1242 Smart batteries may act as master or slave on the bus, but must \ 1243 perform bus master timing arbitration according to the SMBus \ 1244 specification when acting as master. | 1245 """ 1246 constant_elements = [ 1247 {"register": SMBusReg.DEVICE_CHEMISTRY, "requirement": "LION"}, 1248 {"register": SMBusReg.DEVICE_NAME, "requirement": "BB-2590/U"}, 1249 {"register": SMBusReg.MANUFACTURER_NAME, "requirement": "TURN-AROUND FACTOR"}, 1250 {"register": SMBusReg.SERIAL_NUM, "requirement": 0xFFFF}, 1251 {"register": SMBusReg.MANUFACTURER_DATE, "requirement": None}, 1252 {"register": SMBusReg.SPECIFICATION_INFO, "requirement": None}, 1253 {"register": SMBusReg.DESIGN_VOLTAGE, "requirement": 16800}, 1254 { 1255 "register": SMBusReg.DESIGN_CAPACITY, 1256 "requirement": math.floor(0.975 * CELL_CAPACITY_AH * 1000), 1257 }, 1258 {"register": SMBusReg.CHARGING_VOLTAGE, "requirement": 16800}, 1259 {"register": SMBusReg.CHARGING_CURRENT, "requirement": 2000}, 1260 {"register": SMBusReg.MANUFACTURING_ACCESS, "requirement": 0}, 1261 {"register": SMBusReg.REMAINING_TIME_ALARM, "requirement": 10}, 1262 {"register": SMBusReg.REMAINING_CAPACITY, "requirement": 48}, 1263 {"register": SMBusReg.AT_RATE_OK, "requirement": True}, 1264 {"register": SMBusReg.AT_RATE_TIME_TO_EMPTY, "requirement": 65535}, 1265 {"register": SMBusReg.AT_RATE_TIME_TO_FULL, "requirement": 65535}, 1266 {"register": SMBusReg.AT_RATE, "requirement": 0}, 1267 {"register": SMBusReg.BATTERY_MODE, "requirement": 0}, 1268 {"register": SMBusReg.MAX_ERROR, "requirement": 0}, 1269 { 1270 "register": SMBusReg.FULL_CHARGE_CAPACITY, 1271 "requirement": CELL_CAPACITY_AH * 1000, 1272 }, 1273 ] 1274 test_failed = False 1275 1276 for element in constant_elements: 1277 register = element["register"] 1278 requirement = element["requirement"] 1279 1280 read_response = _smbus.read_register(register) 1281 1282 if register == SMBusReg.SERIAL_NUM: 1283 if read_response[0] != requirement: 1284 logger.write_result_to_html_report( 1285 f"{register.fname} had unique value of {read_response[0]} ({read_response[1]})" 1286 ) 1287 else: 1288 logger.write_result_to_html_report( 1289 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1290 f"not a unique value</font>" 1291 ) 1292 test_failed = True 1293 continue 1294 1295 if register == SMBusReg.REMAINING_CAPACITY: 1296 design_capacity = _smbus.read_register(SMBusReg.DESIGN_CAPACITY) 1297 requirement = design_capacity[0] // 2 1298 if requirement - 3 <= read_response[0] <= requirement + 3: 1299 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1300 else: 1301 logger.write_result_to_html_report( 1302 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1303 f"not {requirement}</font>" 1304 ) 1305 test_failed = True 1306 1307 elif isinstance(requirement, int): 1308 if read_response[0] == requirement or read_response[1] == requirement.to_bytes(2, byteorder="little"): 1309 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1310 else: 1311 logger.write_result_to_html_report( 1312 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1313 f"not {requirement}</font>" 1314 ) 1315 test_failed = True 1316 elif read_response[0] == requirement: 1317 logger.write_result_to_html_report(f"{register.fname} had expected value of {requirement}") 1318 else: 1319 logger.write_result_to_html_report( 1320 f'<font color="#990000">{register.fname} was {read_response[0]} ({read_response[1]}) ' 1321 f"not {requirement}</font>" 1322 ) 1323 test_failed = True 1324 1325 if test_failed: 1326 pytest.fail() 1327 1328 logger.write_result_to_html_report("All SMBus Constant Values passed")
| Description | Constant SMBus Values |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#385 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.9.1 (SMBus) |
| Instructions | 1. Read Device Chemistry [0x22] 2. Read Device Name [0x21] 3. Read Manufacturer Name [0x20] 4. Read Serial Number [0x1C] 5. Read Manufacturer Date [0x1B] 6. Read Specification Info [0x1A] 7. Read Design Voltage [0x19] 8. Read Design Capacity [0x18] 9. Read Charging Voltage [0x15] 10. Read Charging Current [0x14] 11. Read Manufacturer Access [0x00] 12. Read Remaining Time Alarm [0x02] 13. Read Remaining Capacity Alarm [0x01] 14. Read AtRateOk [0x07] 15. Read AtRateTimeToEmpty [0x06] 16. Read AtRateTimeToFull [0x05] 17. Read AtRate [0x04] 18. Read Battery Mode [0x03] 19. Read Max Error [0x0C] 20. Read Full Charge Capacity [0x10] |
| Pass / Fail Criteria | Charging (mA) ---- ⦁ Expect Device Chemistry [0x22] to be LION ⦁ Expect Device Name [0x21] to be BB-2590/U ⦁ Expect Manufacturer Name [0x20] to be TURN-AROUND FACTOR ⦁ Expect Serial Number [0x1C] to be a unique value ⦁ Expect Manufacturer Date [0x1B] to be 0x0100 ⦁ Expect Specification Info [0x1A] to be 0x0100 ⦁ Expect Design Voltage [0x19] to be 16800 ⦁ Expect Design Capacity [0x18] to be CAPACITY * 0.975 ⦁ Expect Charging Voltage [0x15] to be 16800 ⦁ Expect Charging Current [0x14] to be 2000 ⦁ Expect Manufacturer Access [0x00] to be 0 ⦁ Expect Remaining Time Alarm [0x02] to be 10 ⦁ Expect Remaining Capacity Alarm [0x01] to be SOC * Design Capacity ⦁ Expect AtRateOk [0x07] to be True ⦁ Expect AtRateTimeToEmpty [0x06] to be 65535 ⦁ Expect AtRateTimeToFull [0x05] to be 65535 ⦁ Expect AtRate [0x04] to be 0 ⦁ Expect Battery Mode [0x03] to be 0 ⦁ Expect Max Error [0x0C] to be 0 ⦁ Expect Full Charge Capacity [0x10] to be CAPACITY |
| Estimated Duration | 10 seconds |
| Note | When specified (see 3.1), batteries shall be compliant with System Management Bus (SMBus) Specification Revision 1.1 and Smart Battery Data (SBData) Specification, version 1.1, with the exception that SBData safety signal hardware requirements therein shall be replaced with a charge enable when a charge enable is specified (see 3.1 and 3.5.6). Certification is required. Batteries shall be compatible with appropriate Level 2 and Level 3 chargers (see 6.4.7). When tested as specified in 4.7.2.15.1, SMBus data output shall be accurate within +0/-5% of the actual state of charge for the battery under test throughout the discharge. Manufacturer and battery data shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V logic circuitry. Pull-up resistors will be provided by the charger. SMBus circuitry shall respond to a SMBus query within 2 seconds. Smart batteries may act as master or slave on the bus, but must perform bus master timing arbitration according to the SMBus specification when acting as master. |
1331class TestVariableSMBusValues: 1332 """Test variable SMBus values""" 1333 1334 class TestCycleCount(CSVRecordEvent): 1335 """@private Compare SMBus Cycle Count to reported count""" 1336 1337 cycle_count = 0 1338 required_value = None 1339 1340 @classmethod 1341 def failed(cls) -> bool: 1342 """Check if test parameters were exceeded""" 1343 return bool(cls.cycle_count != cls.required_value) 1344 1345 @classmethod 1346 def verify(cls, row, _serial_data, _cell_data): 1347 """Set Cycle count value""" 1348 cls.cycle_count = row["Cycle Count"] 1349 if cls.required_value is None: 1350 cls.required_value = cls.cycle_count + 1 1351 1352 @classmethod 1353 def result(cls): 1354 """Detailed test result information""" 1355 return f"Cycle Count: {cls.cmp(cls.cycle_count, '==', cls.required_value or 0, form='d')}" 1356 1357 class TestCurrent(CSVRecordEvent): 1358 """@private Compare SMBus Current value to expected value""" 1359 1360 allowable_error = 0.015 1361 smbus_data = SimpleNamespace(current=0, terminal=0, error=0.0) 1362 1363 @classmethod 1364 def failed(cls) -> bool: 1365 """Check if test parameters were exceeded""" 1366 return bool(cls.smbus_data.error > cls.allowable_error) 1367 1368 @classmethod 1369 def verify(cls, row, _serial_data, _cell_data): 1370 """Set current and expected values""" 1371 row_data = SimpleNamespace(current=row["Current (mA)"], terminal=row["HITL Current (A)"] * 1000) 1372 row_data.error = abs((row_data.current - row_data.terminal) / row_data.terminal) 1373 if abs(row_data.current) > 100: 1374 cls.smbus_data = max(cls.smbus_data, row_data, key=lambda data: data.error) 1375 1376 @classmethod 1377 def result(cls): 1378 """Detailed test result information""" 1379 return ( 1380 f"Current error: {cls.cmp(cls.smbus_data.error, '<=', cls.allowable_error)} " 1381 f"(SMBus Current: {cls.smbus_data.current} mA, HITL Terminal Current: {cls.smbus_data.terminal} mA)" 1382 ) 1383 1384 class TestVoltage(CSVRecordEvent): 1385 """@private Compare HITL Terminal Voltage to reported SMBus voltage""" 1386 1387 allowable_error = 0.015 1388 smbus_data = SimpleNamespace(voltage=0, terminal=0, error=0.0) 1389 1390 @classmethod 1391 def failed(cls) -> bool: 1392 """Check if test parameters were exceeded""" 1393 return bool(cls.smbus_data.error > cls.allowable_error) 1394 1395 @classmethod 1396 def verify(cls, row, _serial_data, _cell_data): 1397 """Voltage within range""" 1398 row_data = SimpleNamespace(voltage=row["Voltage (mV)"], terminal=row["HITL Voltage (V)"] * 1000) 1399 row_data.error = abs((row_data.voltage - row_data.terminal) / row_data.terminal) 1400 cls.smbus_data = max(cls.smbus_data, row_data, key=lambda data: data.error) 1401 1402 @classmethod 1403 def result(cls): 1404 """Detailed test result information.""" 1405 return ( 1406 f"Voltage error: {cls.cmp(cls.smbus_data.error, '<=', cls.allowable_error)} " 1407 f"(SMBus Voltage: {cls.smbus_data.voltage} mV, HITL Terminal Voltage: {cls.smbus_data.terminal} mV)" 1408 ) 1409 1410 class TestTemperature(CSVRecordEvent): 1411 """@private Compare average HITL THERM1 & THERM2 temperatures to reported SMBus Temperature""" 1412 1413 smbus_temperature = 0 1414 average_hitl_temperature = 0 1415 low_temperature_range = 0 1416 high_temperature_range = 0 1417 1418 @classmethod 1419 def failed(cls) -> bool: 1420 """Check if test parameters were exceeded""" 1421 return (cls.smbus_temperature <= cls.low_temperature_range) or ( 1422 cls.smbus_temperature >= cls.high_temperature_range 1423 ) 1424 1425 @classmethod 1426 def verify(cls, row, _serial_data, _cell_data): 1427 """Voltage within range""" 1428 cls.smbus_temperature = row["Temperature (dK)"] / 10 - 273 1429 cls.average_hitl_temperature = statistics.mean([_plateset.thermistor1, _plateset.thermistor2]) 1430 cls.low_temperature_range = cls.average_hitl_temperature - 5 1431 cls.high_temperature_range = cls.average_hitl_temperature + 5 1432 1433 @classmethod 1434 def result(cls): 1435 """Detailed test result information.""" 1436 return ( 1437 f"Temperature Error: " 1438 f"{cls.cmp(cls.smbus_temperature, '>=', cls.low_temperature_range, '°C', '.2f')}, " 1439 f" {cls.cmp(cls.smbus_temperature, '<=', cls.high_temperature_range, '°C', '.2f')}," 1440 f"(SMBus Temperature: {cls.smbus_temperature:.2f} °C, " 1441 f"HITL THERM1 & THERM2 Average Temperature: {cls.average_hitl_temperature:.2f} °C)" 1442 ) 1443 1444 class TestCellVoltage1(CSVRecordEvent): 1445 """@private Compare HITL Cell Voltage 1 to reported Cell Voltage1""" 1446 1447 allowable_error = 0.01 1448 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1449 1450 @classmethod 1451 def failed(cls) -> bool: 1452 """Check if test parameters were exceeded""" 1453 return bool(cls.max.error > cls.allowable_error) 1454 1455 @classmethod 1456 def verify(cls, row, _serial_data, _cell_data): 1457 """Cell Voltage 1 within range""" 1458 row_data = SimpleNamespace( 1459 cell_voltage=row["Cell Voltage1 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 1 Volts (V)"]) * 1000) 1460 ) 1461 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1462 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1463 1464 @classmethod 1465 def result(cls): 1466 """Detailed test result information.""" 1467 return ( 1468 f"Cell Voltage1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1469 f"(SMBus Cell Voltage1: {cls.max.cell_voltage:.2f} mV, HITL Cell Sim 1 Voltage: " 1470 f"{cls.max.hitl_cell_voltage:.2f} mV)" 1471 ) 1472 1473 class TestCellVoltage2(CSVRecordEvent): 1474 """@private Compare HITL Cell Voltage 2 to reported Cell Voltage2""" 1475 1476 allowable_error = 0.01 1477 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1478 1479 @classmethod 1480 def failed(cls) -> bool: 1481 """Check if test parameters were exceeded""" 1482 return bool(cls.max.error > cls.allowable_error) 1483 1484 @classmethod 1485 def verify(cls, row, _serial_data, _cell_data): 1486 """Cell Voltage 2 within range""" 1487 row_data = SimpleNamespace( 1488 cell_voltage=row["Cell Voltage2 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 2 Volts (V)"]) * 1000) 1489 ) 1490 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1491 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1492 1493 @classmethod 1494 def result(cls): 1495 """Detailed test result information.""" 1496 return ( 1497 f"Cell Voltage2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1498 f"(SMBus Cell Voltage2: {cls.max.cell_voltage:.2f} mV, " 1499 f"HITL Cell Sim 2 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1500 ) 1501 1502 class TestCellVoltage3(CSVRecordEvent): 1503 """@private Compare HITL Cell Voltage 3 to reported Cell Voltage3""" 1504 1505 allowable_error = 0.01 1506 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1507 1508 @classmethod 1509 def failed(cls) -> bool: 1510 """Check if test parameters were exceeded""" 1511 return bool(cls.max.error > cls.allowable_error) 1512 1513 @classmethod 1514 def verify(cls, row, _serial_data, _cell_data): 1515 """Cell Voltage 3 within range""" 1516 row_data = SimpleNamespace( 1517 cell_voltage=row["Cell Voltage3 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 3 Volts (V)"]) * 1000) 1518 ) 1519 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1520 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1521 1522 @classmethod 1523 def result(cls): 1524 """Detailed test result information.""" 1525 return ( 1526 f"Cell Voltage3 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1527 f"(SMBus Cell Voltage3: {cls.max.cell_voltage:.2f} mV, " 1528 f"HITL Cell Sim 3 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1529 ) 1530 1531 class TestCellVoltage4(CSVRecordEvent): 1532 """@private Compare HITL Cell Voltage 4 to reported Cell Voltage4""" 1533 1534 allowable_error = 0.01 1535 max = SimpleNamespace(cell_voltage=0, hitl_cell_voltage=0, error=0.0) 1536 1537 @classmethod 1538 def failed(cls) -> bool: 1539 """Check if test parameters were exceeded""" 1540 return bool(cls.max.error > cls.allowable_error) 1541 1542 @classmethod 1543 def verify(cls, row, _serial_data, _cell_data): 1544 """Cell Voltage 4 within range""" 1545 row_data = SimpleNamespace( 1546 cell_voltage=row["Cell Voltage4 (mV)"], hitl_cell_voltage=(float(row["Cell Sim 4 Volts (V)"]) * 1000) 1547 ) 1548 row_data.error = abs((row_data.cell_voltage - row_data.hitl_cell_voltage) / row_data.hitl_cell_voltage) 1549 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1550 1551 @classmethod 1552 def result(cls): 1553 """Detailed test result information.""" 1554 return ( 1555 f"Cell Voltage4 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)}" 1556 f"(SMBus Cell Voltage4: {cls.max.cell_voltage:.2f} mV, " 1557 f"HITL Cell Sim 4 Voltage: {cls.max.hitl_cell_voltage:.2f} mV)" 1558 ) 1559 1560 class TestStateOfCharge(CSVRecordEvent): 1561 """@private Compare HITL State of Charge to reported SMBus State of Charge""" 1562 1563 allowable_error = 5 1564 max = SimpleNamespace(absolute_charge=0, hitl_charge=0, error=0.0) 1565 1566 @classmethod 1567 def failed(cls) -> bool: 1568 """Check if test parameters were exceeded""" 1569 return bool(cls.max.error > cls.allowable_error) 1570 1571 @classmethod 1572 def verify(cls, row, _serial_data, _cell_data): 1573 """State of Charge within range""" 1574 row_data = SimpleNamespace(absolute_charge=row["Absolute State Of Charge (%)"]) 1575 row_data.hitl_charge = min( 1576 float(row["Cell Sim 1 SOC (%)"].replace("%", "")), 1577 float(row["Cell Sim 2 SOC (%)"].replace("%", "")), 1578 float(row["Cell Sim 3 SOC (%)"].replace("%", "")), 1579 float(row["Cell Sim 4 SOC (%)"].replace("%", "")), 1580 ) 1581 row_data.error = abs((row_data.absolute_charge - row_data.hitl_charge)) 1582 cls.max = max(cls.max, row_data, key=lambda data: data.error) 1583 1584 @classmethod 1585 def result(cls): 1586 """Detailed test result information.""" 1587 return ( 1588 f"State of Charge error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '%', '.1f')}" 1589 f"(SMBus Absolute State of Charge: {cls.max.absolute_charge:.2f} %, " 1590 f"Lowest HITL State of Charge: {cls.max.hitl_charge:.2f} %)" 1591 ) 1592 1593 def test_variable_smbus_values(self): 1594 """ 1595 | Description | Variable SMBus Values | 1596 | :------------------- | :--------------------------------------------------------------------- | 1597 | GitHub Issue | turnaroundfactor/HITL#385 | 1598 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1599jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1600 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1601 | Instructions | 1. Set thermistors to 23°C </br>\ 1602 2. Put cells in a rested state at 2.5V per cell </br>\ 1603 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 1604 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours)</br>\ 1605 5. Discharge at 2A until battery reaches 10V then stop discharging | 1606 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1607 ⦁ Expect Cycle Count [0x17] to be 0 </br>\ 1608 ⦁ Expect Current [0x0A] to be within 1% for abs(Terminal Current > 100mA) </br>\ 1609 ⦁ Expect Voltage [0x09] to be HITL Terminal Voltage within 1% </br>\ 1610 ⦁ Expect Temperature [0x08] to be average of HITL THERM1 & THERM2 within 5°C </br>\ 1611 ⦁ Expect Cell Voltage1 [0x3C] to be HITL Cell Sim 1 Volts (V) </br>\ 1612 ⦁ Expect Cell Voltage2 [0x3D] to be HITL Cell Sim 2 Volts (V) </br>\ 1613 ⦁ Expect Cell Voltage3 [0x3E] to be HITL Cell Sim 3 Volts (V) </br>\ 1614 ⦁ Expect Cell Voltage4 [0x3F] to be HITL Cell Sim 4 Volts (V) </br>\ 1615 ⦁ Expect State of Charge [0x4F] to be HITL State of Charge | 1616 | Estimated Duration | 18 minutes | 1617 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1618 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1619 Data (SBData) Specification, version 1.1, with the exception that \ 1620 SBData safety signal hardware requirements therein shall be replaced \ 1621 with a charge enable when a charge enable is specified (see 3.1 and \ 1622 3.5.6). Certification is required. Batteries shall be compatible \ 1623 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1624 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1625 accurate within +0/-5% of the actual state of charge for the battery \ 1626 under test throughout the discharge. Manufacturer and battery data \ 1627 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1628 logic circuitry. Pull-up resistors will be provided by the charger. \ 1629 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1630 Smart batteries may act as master or slave on the bus, but must \ 1631 perform bus master timing arbitration according to the SMBus \ 1632 specification when acting as master. | 1633 """ 1634 1635 standard_charge() 1636 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 1637 standard_discharge() 1638 1639 # Check results 1640 if CSVRecordEvent.failed(): 1641 pytest.fail(CSVRecordEvent.result())
Test variable SMBus values
1593 def test_variable_smbus_values(self): 1594 """ 1595 | Description | Variable SMBus Values | 1596 | :------------------- | :--------------------------------------------------------------------- | 1597 | GitHub Issue | turnaroundfactor/HITL#385 | 1598 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1599jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D12) | 1600 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 1601 | Instructions | 1. Set thermistors to 23°C </br>\ 1602 2. Put cells in a rested state at 2.5V per cell </br>\ 1603 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 1604 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours)</br>\ 1605 5. Discharge at 2A until battery reaches 10V then stop discharging | 1606 | Pass / Fail Criteria | Charging (mA) ---- </br>\ 1607 ⦁ Expect Cycle Count [0x17] to be 0 </br>\ 1608 ⦁ Expect Current [0x0A] to be within 1% for abs(Terminal Current > 100mA) </br>\ 1609 ⦁ Expect Voltage [0x09] to be HITL Terminal Voltage within 1% </br>\ 1610 ⦁ Expect Temperature [0x08] to be average of HITL THERM1 & THERM2 within 5°C </br>\ 1611 ⦁ Expect Cell Voltage1 [0x3C] to be HITL Cell Sim 1 Volts (V) </br>\ 1612 ⦁ Expect Cell Voltage2 [0x3D] to be HITL Cell Sim 2 Volts (V) </br>\ 1613 ⦁ Expect Cell Voltage3 [0x3E] to be HITL Cell Sim 3 Volts (V) </br>\ 1614 ⦁ Expect Cell Voltage4 [0x3F] to be HITL Cell Sim 4 Volts (V) </br>\ 1615 ⦁ Expect State of Charge [0x4F] to be HITL State of Charge | 1616 | Estimated Duration | 18 minutes | 1617 | Note | When specified (see 3.1), batteries shall be compliant with System \ 1618 Management Bus (SMBus) Specification Revision 1.1 and Smart Battery \ 1619 Data (SBData) Specification, version 1.1, with the exception that \ 1620 SBData safety signal hardware requirements therein shall be replaced \ 1621 with a charge enable when a charge enable is specified (see 3.1 and \ 1622 3.5.6). Certification is required. Batteries shall be compatible \ 1623 with appropriate Level 2 and Level 3 chargers (see 6.4.7). \ 1624 When tested as specified in 4.7.2.15.1, SMBus data output shall be \ 1625 accurate within +0/-5% of the actual state of charge for the battery \ 1626 under test throughout the discharge. Manufacturer and battery data \ 1627 shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V \ 1628 logic circuitry. Pull-up resistors will be provided by the charger. \ 1629 SMBus circuitry shall respond to a SMBus query within 2 seconds. \ 1630 Smart batteries may act as master or slave on the bus, but must \ 1631 perform bus master timing arbitration according to the SMBus \ 1632 specification when acting as master. | 1633 """ 1634 1635 standard_charge() 1636 standard_rest(seconds=30 if FAST_MODE else 3.1 * 3600) 1637 standard_discharge() 1638 1639 # Check results 1640 if CSVRecordEvent.failed(): 1641 pytest.fail(CSVRecordEvent.result())
| Description | Variable SMBus Values |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#385 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.9.1 (SMBus) |
| Instructions | 1. Set thermistors to 23°C 2. Put cells in a rested state at 2.5V per cell 3. Charge battery (16.8V / 3A / 100 mA cutoff) 4. Rest for 3.1 hours (we want to calculate SoH change which requires 3 hours) 5. Discharge at 2A until battery reaches 10V then stop discharging |
| Pass / Fail Criteria | Charging (mA) ---- ⦁ Expect Cycle Count [0x17] to be 0 ⦁ Expect Current [0x0A] to be within 1% for abs(Terminal Current > 100mA) ⦁ Expect Voltage [0x09] to be HITL Terminal Voltage within 1% ⦁ Expect Temperature [0x08] to be average of HITL THERM1 & THERM2 within 5°C ⦁ Expect Cell Voltage1 [0x3C] to be HITL Cell Sim 1 Volts (V) ⦁ Expect Cell Voltage2 [0x3D] to be HITL Cell Sim 2 Volts (V) ⦁ Expect Cell Voltage3 [0x3E] to be HITL Cell Sim 3 Volts (V) ⦁ Expect Cell Voltage4 [0x3F] to be HITL Cell Sim 4 Volts (V) ⦁ Expect State of Charge [0x4F] to be HITL State of Charge |
| Estimated Duration | 18 minutes |
| Note | When specified (see 3.1), batteries shall be compliant with System Management Bus (SMBus) Specification Revision 1.1 and Smart Battery Data (SBData) Specification, version 1.1, with the exception that SBData safety signal hardware requirements therein shall be replaced with a charge enable when a charge enable is specified (see 3.1 and 3.5.6). Certification is required. Batteries shall be compatible with appropriate Level 2 and Level 3 chargers (see 6.4.7). When tested as specified in 4.7.2.15.1, SMBus data output shall be accurate within +0/-5% of the actual state of charge for the battery under test throughout the discharge. Manufacturer and battery data shall be correctly programmed (see 4.7.2.15.1). SMBus shall use 5V logic circuitry. Pull-up resistors will be provided by the charger. SMBus circuitry shall respond to a SMBus query within 2 seconds. Smart batteries may act as master or slave on the bus, but must perform bus master timing arbitration according to the SMBus specification when acting as master. |
1644@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 1645class TestChargeAcceptance(CSVRecordEvent): 1646 """Run a test for charge acceptance""" 1647 1648 def test_charge_acceptance(self): 1649 """ 1650 | Description | Charge Acceptance | 1651 | :------------------- | :--------------------------------------------------------------------- | 1652 | GitHub Issue | turnaroundfactor/HITL#517 | 1653 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1654jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D29) | 1655 | MIL-PRF Sections | 3.5.10.1 (Charge Acceptance) | 1656 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1657 2. Put cells in rested state at 4.2V per cell </br>\ 1658 3. Set THERM1 and THERM2 to -20°C </br>\ 1659 4. Attempt to charge at 1A </br>\ 1660 5. Attempt to discharge at 1A </br>\ 1661 6. Set THERM1 and THERM2 to 50°C </br>\ 1662 7. Attempt to charge at 1A </br>\ 1663 8. Attempt to discharge at 1A | 1664 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -20°C +/- 1.1°C </br>\ 1665 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1666 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA </br>\ 1667 ⦁ Expect Serial THERM1 & THERM 2 to be 50°C +/- 1.1°C </br>\ 1668 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1669 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 1670 | Estimated Duration | 17 seconds | 1671 | Note | When tested as specified in 4.7.2.9, batteries shall meet the \ 1672 capacity requirements 3.5.3 and the visual mechanical requirements \ 1673 of TABLE XIV. The surface temperature of the battery shall not \ 1674 exceed 185°F (85°C) | 1675 """ 1676 1677 failed_tests = [] 1678 temperatures = [-20, 50] 1679 1680 for set_temp in temperatures: 1681 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 1682 1683 _plateset.disengage_safety_protocols = True 1684 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 1685 _plateset.disengage_safety_protocols = False 1686 1687 time.sleep(2) 1688 1689 # Get the serial data 1690 serial_data = serial_monitor.read() 1691 1692 # Convert temperature to Celsius from Kelvin 1693 therm_one = serial_data["dk_temp"] / 10 - 273 1694 therm_two = serial_data["dk_temp1"] / 10 - 273 1695 temp_range = f"{set_temp}°C +/- 1.1°C" 1696 low_range = set_temp - 1.1 1697 high_range = set_temp + 1.1 1698 1699 if low_range <= therm_one <= high_range: 1700 logger.write_result_to_html_report( 1701 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 1702 ) 1703 else: 1704 logger.write_result_to_html_report( 1705 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1706 f"of {temp_range}</font>" 1707 ) 1708 failed_tests.append("THERM1") 1709 1710 if low_range <= therm_two <= high_range: 1711 logger.write_result_to_html_report( 1712 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 1713 ) 1714 else: 1715 logger.write_result_to_html_report( 1716 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1717 f"expected range of {temp_range}</font>" 1718 ) 1719 failed_tests.append("THERM2") 1720 1721 logger.write_info_to_report("Attempting to charge at 1A") 1722 limit = 0.03 1723 with _bms.charger(16.8, 1): 1724 time.sleep(1) 1725 charger_amps = _bms.charger.amps 1726 low_range = 1 - limit 1727 high_range = 1 + limit 1728 expected_current_range = f"1A +/- {limit}A" 1729 if low_range <= charger_amps <= high_range: 1730 logger.write_result_to_html_report( 1731 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 1732 f"expected range of {expected_current_range}" 1733 ) 1734 else: 1735 logger.write_result_to_html_report( 1736 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 1737 f"which was not within the expected range of {expected_current_range} </font>" 1738 ) 1739 failed_tests.append("HITL Terminal Current") 1740 1741 logger.write_info_to_report("Attempting to discharge at 1A") 1742 with _bms.load(1): 1743 time.sleep(1) 1744 load_amps = -1 * _bms.load.amps 1745 low_range = -1 - limit 1746 high_range = -1 + limit 1747 expected_current_range = f"-1A +/- {limit}A" 1748 if low_range <= load_amps <= high_range: 1749 logger.write_result_to_html_report( 1750 f"HITL Terminal Current was {load_amps:.3f}A after discharging, which was within the " 1751 f"expected range of {expected_current_range}" 1752 ) 1753 else: 1754 logger.write_result_to_html_report( 1755 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A after discharging, ' 1756 f"which was not within the expected range of {expected_current_range} </font>" 1757 ) 1758 failed_tests.append("HITL Terminal Current") 1759 1760 if len(failed_tests) > 0: 1761 pytest.fail() 1762 1763 logger.write_result_to_html_report("All checks passed test")
Run a test for charge acceptance
1648 def test_charge_acceptance(self): 1649 """ 1650 | Description | Charge Acceptance | 1651 | :------------------- | :--------------------------------------------------------------------- | 1652 | GitHub Issue | turnaroundfactor/HITL#517 | 1653 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1654jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D29) | 1655 | MIL-PRF Sections | 3.5.10.1 (Charge Acceptance) | 1656 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1657 2. Put cells in rested state at 4.2V per cell </br>\ 1658 3. Set THERM1 and THERM2 to -20°C </br>\ 1659 4. Attempt to charge at 1A </br>\ 1660 5. Attempt to discharge at 1A </br>\ 1661 6. Set THERM1 and THERM2 to 50°C </br>\ 1662 7. Attempt to charge at 1A </br>\ 1663 8. Attempt to discharge at 1A | 1664 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -20°C +/- 1.1°C </br>\ 1665 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1666 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA </br>\ 1667 ⦁ Expect Serial THERM1 & THERM 2 to be 50°C +/- 1.1°C </br>\ 1668 ⦁ Expect HITL Terminal Current to be 1A +/- 30mA </br>\ 1669 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 1670 | Estimated Duration | 17 seconds | 1671 | Note | When tested as specified in 4.7.2.9, batteries shall meet the \ 1672 capacity requirements 3.5.3 and the visual mechanical requirements \ 1673 of TABLE XIV. The surface temperature of the battery shall not \ 1674 exceed 185°F (85°C) | 1675 """ 1676 1677 failed_tests = [] 1678 temperatures = [-20, 50] 1679 1680 for set_temp in temperatures: 1681 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 1682 1683 _plateset.disengage_safety_protocols = True 1684 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 1685 _plateset.disengage_safety_protocols = False 1686 1687 time.sleep(2) 1688 1689 # Get the serial data 1690 serial_data = serial_monitor.read() 1691 1692 # Convert temperature to Celsius from Kelvin 1693 therm_one = serial_data["dk_temp"] / 10 - 273 1694 therm_two = serial_data["dk_temp1"] / 10 - 273 1695 temp_range = f"{set_temp}°C +/- 1.1°C" 1696 low_range = set_temp - 1.1 1697 high_range = set_temp + 1.1 1698 1699 if low_range <= therm_one <= high_range: 1700 logger.write_result_to_html_report( 1701 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 1702 ) 1703 else: 1704 logger.write_result_to_html_report( 1705 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1706 f"of {temp_range}</font>" 1707 ) 1708 failed_tests.append("THERM1") 1709 1710 if low_range <= therm_two <= high_range: 1711 logger.write_result_to_html_report( 1712 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 1713 ) 1714 else: 1715 logger.write_result_to_html_report( 1716 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1717 f"expected range of {temp_range}</font>" 1718 ) 1719 failed_tests.append("THERM2") 1720 1721 logger.write_info_to_report("Attempting to charge at 1A") 1722 limit = 0.03 1723 with _bms.charger(16.8, 1): 1724 time.sleep(1) 1725 charger_amps = _bms.charger.amps 1726 low_range = 1 - limit 1727 high_range = 1 + limit 1728 expected_current_range = f"1A +/- {limit}A" 1729 if low_range <= charger_amps <= high_range: 1730 logger.write_result_to_html_report( 1731 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 1732 f"expected range of {expected_current_range}" 1733 ) 1734 else: 1735 logger.write_result_to_html_report( 1736 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 1737 f"which was not within the expected range of {expected_current_range} </font>" 1738 ) 1739 failed_tests.append("HITL Terminal Current") 1740 1741 logger.write_info_to_report("Attempting to discharge at 1A") 1742 with _bms.load(1): 1743 time.sleep(1) 1744 load_amps = -1 * _bms.load.amps 1745 low_range = -1 - limit 1746 high_range = -1 + limit 1747 expected_current_range = f"-1A +/- {limit}A" 1748 if low_range <= load_amps <= high_range: 1749 logger.write_result_to_html_report( 1750 f"HITL Terminal Current was {load_amps:.3f}A after discharging, which was within the " 1751 f"expected range of {expected_current_range}" 1752 ) 1753 else: 1754 logger.write_result_to_html_report( 1755 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A after discharging, ' 1756 f"which was not within the expected range of {expected_current_range} </font>" 1757 ) 1758 failed_tests.append("HITL Terminal Current") 1759 1760 if len(failed_tests) > 0: 1761 pytest.fail() 1762 1763 logger.write_result_to_html_report("All checks passed test")
| Description | Charge Acceptance |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#517 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.10.1 (Charge Acceptance) |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 4.2V per cell 3. Set THERM1 and THERM2 to -20°C 4. Attempt to charge at 1A 5. Attempt to discharge at 1A 6. Set THERM1 and THERM2 to 50°C 7. Attempt to charge at 1A 8. Attempt to discharge at 1A |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -20°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be 1A +/- 30mA ⦁ Expect HITL Terminal Current to be -1A +/- 30mA ⦁ Expect Serial THERM1 & THERM 2 to be 50°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be 1A +/- 30mA ⦁ Expect HITL Terminal Current to be -1A +/- 30mA |
| Estimated Duration | 17 seconds |
| Note | When tested as specified in 4.7.2.9, batteries shall meet the capacity requirements 3.5.3 and the visual mechanical requirements of TABLE XIV. The surface temperature of the battery shall not exceed 185°F (85°C) |
1766@pytest.mark.parametrize("reset_test_environment", [{"volts": 4.2}], indirect=True) 1767class TestHighTemperaturePermanentCutoff(CSVRecordEvent): 1768 """Run a test for High temperature permanent cutoff""" 1769 1770 def test_high_temp_perm_cutoff(self): 1771 """ 1772 | Description | High Temperature Permanent Cutoff | 1773 | :------------------- | :--------------------------------------------------------------------- | 1774 | GitHub Issue | turnaroundfactor/HITL#516 | 1775 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1776jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 1777 | MIL-PRF Sections | 3.7.2.5 (High Temperature permanent cut off devices) | 1778 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1779 2. Put cells in rested state at 4.2V per cell </br>\ 1780 3. Set THERM1 and THERM2 to 95°C </br>\ 1781 4. Measure Voltage </br>\ 1782 5. Attempt to charge at 1A </br>\ 1783 6. Attempt to discharge at 1A </br>\ 1784 7. Set temperature to 40°C </br>\ 1785 8. Measure Voltage | 1786 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 95°C +/- 5°C </br>\ 1787 ⦁ Expect Overtemperature Permanent Disable flag to be set </br>\ 1788 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V </br>\ 1789 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1790 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1791 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V | 1792 | Estimated Duration | 6 seconds | 1793 | Note | The following test shall be performed. Charge batteries as specified \ 1794 in 4.6; use of 4.6.3 is permitted. Each battery shall be permanently \ 1795 shut off when the temperature of the battery reaches 199 ± 9°F \ 1796 (93 ± 5°C). The device shall prevent charging and discharging of the \ 1797 battery. The minimum quantity shall be as specified in the applicable \ 1798 specification sheet. When tested as specified in 4.7.4.10, battery \ 1799 voltage shall be zero volts after high temperature storage and shall \ 1800 remain at zero after 104°F (40°C) storage. | 1801 """ 1802 1803 failed_tests = [] 1804 timeout_seconds = 30 1805 1806 logger.write_info_to_report("Setting THERM1 & THERM2 to 95°C") 1807 _plateset.disengage_safety_protocols = True 1808 _plateset.thermistor1 = _plateset.thermistor2 = 95 1809 _plateset.disengage_safety_protocols = False 1810 1811 time.sleep(1) 1812 serial_data = serial_monitor.read() # Get the serial data 1813 # Convert temperature to Celsius from Kelvin 1814 therm_one = serial_data["dk_temp"] / 10 - 273 1815 therm_two = serial_data["dk_temp1"] / 10 - 273 1816 1817 if 90 <= therm_one <= 100: 1818 logger.write_result_to_html_report( 1819 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1820 ) 1821 else: 1822 logger.write_result_to_html_report( 1823 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1824 f"of 95°C +/- 5°C</font>" 1825 ) 1826 failed_tests.append("THERM1") 1827 1828 if 90 <= therm_two <= 100: 1829 logger.write_result_to_html_report( 1830 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1831 ) 1832 else: 1833 logger.write_result_to_html_report( 1834 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1835 f"expected range of 95°C +/- 5°C</font>" 1836 ) 1837 failed_tests.append("THERM2") 1838 1839 start = time.perf_counter() 1840 while (serial_data := serial_monitor.read()) and not serial_data["flags.permanentdisable_overtemp"]: 1841 if time.perf_counter() - start > timeout_seconds: 1842 message = f"Over-temperature permanent disable was not raised after {timeout_seconds} seconds." 1843 logger.write_failure_to_html_report(message) 1844 failed_tests.append("Over-temperature permanent disable flag.") 1845 break 1846 else: 1847 logger.write_result_to_html_report("Over-temperature permanent disable was properly set.") 1848 1849 logger.write_info_to_report("Measuring voltage...") 1850 time.sleep(1) 1851 1852 volt_range = "0V (-0.2V / +3V)" 1853 1854 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1855 if not -0.2 <= _bms.dmm.volts <= 3: 1856 logger.write_failure_to_html_report( 1857 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1858 f"which was not within the expected range of {volt_range}" 1859 ) 1860 failed_tests.append("HITL Terminal Voltage after temperature was set to 95°C") 1861 else: 1862 logger.write_result_to_html_report( 1863 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1864 f"which was within the expected range of {volt_range}" 1865 ) 1866 1867 logger.write_info_to_report("Attempting to charge at 1A") 1868 with _bms.charger(16.8, 1.0): 1869 time.sleep(1) 1870 if -0.020 <= _bms.charger.amps <= 0.020: 1871 logger.write_result_to_html_report( 1872 f"HITL Terminal Current was {_bms.charger.amps:.3f}A, which was within the " 1873 f"expected range of 0A +/- 20mA" 1874 ) 1875 else: 1876 logger.write_result_to_html_report( 1877 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A, which was' 1878 f" not within the expected range of 0A +/- 20mA </font>" 1879 ) 1880 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 1881 1882 logger.write_info_to_report("Attempting to discharge at 1A") 1883 1884 with _bms.load(1): 1885 time.sleep(1) 1886 if -0.020 <= _bms.load.amps <= 0.020: 1887 logger.write_result_to_html_report( 1888 f"HITL Terminal Current was {_bms.load.amps:.3f}A, which was within the " 1889 f"expected range of 0A +/- 20mA" 1890 ) 1891 else: 1892 logger.write_result_to_html_report( 1893 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A, ' 1894 f"which was not within the expected range of 0A +/- 20mA </font>" 1895 ) 1896 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 1897 1898 logger.write_info_to_report("Setting Temperature to 40°C") 1899 _plateset.thermistor1 = _plateset.thermistor2 = 40 1900 1901 logger.write_info_to_report("Measuring voltage...") 1902 time.sleep(1) 1903 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1904 if not -0.2 <= _bms.dmm.volts <= 3: 1905 logger.write_failure_to_html_report( 1906 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1907 f"which was not within the expected range of {volt_range}" 1908 ) 1909 failed_tests.append("HITL Terminal Voltage after temperature was set to 40°C") 1910 else: 1911 logger.write_result_to_html_report( 1912 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V," 1913 f"which was within the expected range of {volt_range}" 1914 ) 1915 1916 if len(failed_tests) > 0: 1917 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 1918 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 1919 pytest.fail(message) 1920 1921 logger.write_result_to_html_report("All checks passed test")
Run a test for High temperature permanent cutoff
1770 def test_high_temp_perm_cutoff(self): 1771 """ 1772 | Description | High Temperature Permanent Cutoff | 1773 | :------------------- | :--------------------------------------------------------------------- | 1774 | GitHub Issue | turnaroundfactor/HITL#516 | 1775 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1776jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 1777 | MIL-PRF Sections | 3.7.2.5 (High Temperature permanent cut off devices) | 1778 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1779 2. Put cells in rested state at 4.2V per cell </br>\ 1780 3. Set THERM1 and THERM2 to 95°C </br>\ 1781 4. Measure Voltage </br>\ 1782 5. Attempt to charge at 1A </br>\ 1783 6. Attempt to discharge at 1A </br>\ 1784 7. Set temperature to 40°C </br>\ 1785 8. Measure Voltage | 1786 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 95°C +/- 5°C </br>\ 1787 ⦁ Expect Overtemperature Permanent Disable flag to be set </br>\ 1788 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V </br>\ 1789 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1790 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1791 ⦁ HITL Terminal Voltage is 0V +/- 0.2 V | 1792 | Estimated Duration | 6 seconds | 1793 | Note | The following test shall be performed. Charge batteries as specified \ 1794 in 4.6; use of 4.6.3 is permitted. Each battery shall be permanently \ 1795 shut off when the temperature of the battery reaches 199 ± 9°F \ 1796 (93 ± 5°C). The device shall prevent charging and discharging of the \ 1797 battery. The minimum quantity shall be as specified in the applicable \ 1798 specification sheet. When tested as specified in 4.7.4.10, battery \ 1799 voltage shall be zero volts after high temperature storage and shall \ 1800 remain at zero after 104°F (40°C) storage. | 1801 """ 1802 1803 failed_tests = [] 1804 timeout_seconds = 30 1805 1806 logger.write_info_to_report("Setting THERM1 & THERM2 to 95°C") 1807 _plateset.disengage_safety_protocols = True 1808 _plateset.thermistor1 = _plateset.thermistor2 = 95 1809 _plateset.disengage_safety_protocols = False 1810 1811 time.sleep(1) 1812 serial_data = serial_monitor.read() # Get the serial data 1813 # Convert temperature to Celsius from Kelvin 1814 therm_one = serial_data["dk_temp"] / 10 - 273 1815 therm_two = serial_data["dk_temp1"] / 10 - 273 1816 1817 if 90 <= therm_one <= 100: 1818 logger.write_result_to_html_report( 1819 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1820 ) 1821 else: 1822 logger.write_result_to_html_report( 1823 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1824 f"of 95°C +/- 5°C</font>" 1825 ) 1826 failed_tests.append("THERM1") 1827 1828 if 90 <= therm_two <= 100: 1829 logger.write_result_to_html_report( 1830 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of 95°C +/- 5°C" 1831 ) 1832 else: 1833 logger.write_result_to_html_report( 1834 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1835 f"expected range of 95°C +/- 5°C</font>" 1836 ) 1837 failed_tests.append("THERM2") 1838 1839 start = time.perf_counter() 1840 while (serial_data := serial_monitor.read()) and not serial_data["flags.permanentdisable_overtemp"]: 1841 if time.perf_counter() - start > timeout_seconds: 1842 message = f"Over-temperature permanent disable was not raised after {timeout_seconds} seconds." 1843 logger.write_failure_to_html_report(message) 1844 failed_tests.append("Over-temperature permanent disable flag.") 1845 break 1846 else: 1847 logger.write_result_to_html_report("Over-temperature permanent disable was properly set.") 1848 1849 logger.write_info_to_report("Measuring voltage...") 1850 time.sleep(1) 1851 1852 volt_range = "0V (-0.2V / +3V)" 1853 1854 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1855 if not -0.2 <= _bms.dmm.volts <= 3: 1856 logger.write_failure_to_html_report( 1857 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1858 f"which was not within the expected range of {volt_range}" 1859 ) 1860 failed_tests.append("HITL Terminal Voltage after temperature was set to 95°C") 1861 else: 1862 logger.write_result_to_html_report( 1863 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1864 f"which was within the expected range of {volt_range}" 1865 ) 1866 1867 logger.write_info_to_report("Attempting to charge at 1A") 1868 with _bms.charger(16.8, 1.0): 1869 time.sleep(1) 1870 if -0.020 <= _bms.charger.amps <= 0.020: 1871 logger.write_result_to_html_report( 1872 f"HITL Terminal Current was {_bms.charger.amps:.3f}A, which was within the " 1873 f"expected range of 0A +/- 20mA" 1874 ) 1875 else: 1876 logger.write_result_to_html_report( 1877 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A, which was' 1878 f" not within the expected range of 0A +/- 20mA </font>" 1879 ) 1880 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 1881 1882 logger.write_info_to_report("Attempting to discharge at 1A") 1883 1884 with _bms.load(1): 1885 time.sleep(1) 1886 if -0.020 <= _bms.load.amps <= 0.020: 1887 logger.write_result_to_html_report( 1888 f"HITL Terminal Current was {_bms.load.amps:.3f}A, which was within the " 1889 f"expected range of 0A +/- 20mA" 1890 ) 1891 else: 1892 logger.write_result_to_html_report( 1893 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A, ' 1894 f"which was not within the expected range of 0A +/- 20mA </font>" 1895 ) 1896 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 1897 1898 logger.write_info_to_report("Setting Temperature to 40°C") 1899 _plateset.thermistor1 = _plateset.thermistor2 = 40 1900 1901 logger.write_info_to_report("Measuring voltage...") 1902 time.sleep(1) 1903 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 1904 if not -0.2 <= _bms.dmm.volts <= 3: 1905 logger.write_failure_to_html_report( 1906 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 1907 f"which was not within the expected range of {volt_range}" 1908 ) 1909 failed_tests.append("HITL Terminal Voltage after temperature was set to 40°C") 1910 else: 1911 logger.write_result_to_html_report( 1912 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V," 1913 f"which was within the expected range of {volt_range}" 1914 ) 1915 1916 if len(failed_tests) > 0: 1917 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 1918 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 1919 pytest.fail(message) 1920 1921 logger.write_result_to_html_report("All checks passed test")
| Description | High Temperature Permanent Cutoff |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#516 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.7.2.5 (High Temperature permanent cut off devices) |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 4.2V per cell 3. Set THERM1 and THERM2 to 95°C 4. Measure Voltage 5. Attempt to charge at 1A 6. Attempt to discharge at 1A 7. Set temperature to 40°C 8. Measure Voltage |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 95°C +/- 5°C ⦁ Expect Overtemperature Permanent Disable flag to be set ⦁ HITL Terminal Voltage is 0V +/- 0.2 V ⦁ HITL Terminal Current is 0A +/- 1mA after charging ⦁ HITL Terminal Current is 0A +/- 1mA after discharging ⦁ HITL Terminal Voltage is 0V +/- 0.2 V |
| Estimated Duration | 6 seconds |
| Note | The following test shall be performed. Charge batteries as specified in 4.6; use of 4.6.3 is permitted. Each battery shall be permanently shut off when the temperature of the battery reaches 199 ± 9°F (93 ± 5°C). The device shall prevent charging and discharging of the battery. The minimum quantity shall be as specified in the applicable specification sheet. When tested as specified in 4.7.4.10, battery voltage shall be zero volts after high temperature storage and shall remain at zero after 104°F (40°C) storage. |
1924@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 1925class TestHighTemperatureTemporaryCutoff(CSVRecordEvent): 1926 """Run a test for High temperature temporary cutoff""" 1927 1928 def test_high_temperature_temp_cutoff(self): 1929 """ 1930 | Description | High Temperature Temporary Cutoff | 1931 | :------------------- | :--------------------------------------------------------------------- | 1932 | GitHub Issue | turnaroundfactor/HITL#517 | 1933 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1934jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D27) | 1935 | MIL-PRF Sections | 3.7.2.4 (High Temperature permanent cut off devices) | 1936 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1937 2. Put cells in rested state at 4.2V per cell </br>\ 1938 3. Set THERM1 and THERM2 to 75°C </br>\ 1939 4. Measure Voltage </br>\ 1940 5. Attempt to charge at 1A </br>\ 1941 6. Attempt to discharge at 1A </br>\ 1942 7. Set temperature to 20°C </br>\ 1943 8. Measure Voltage | 1944 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 75°C +/- 5°C </br>\ 1945 ⦁ Expect Overtemp charge & Overtemp discharge flags to be set </br>\ 1946 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V </br>\ 1947 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1948 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1949 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V | 1950 | Estimated Duration | 12 seconds | 1951 | Note | Each battery shall contain a minimum quantity of normally closed \ 1952 thermoswitches that shall open at 158 ± 9°F (70 ± 5°C) and close at \ 1953 122 ± 9°F (50 ± 5°C). Each thermoswitch shall make physical contact \ 1954 with not less than one cell. The minimum quantity shall be as \ 1955 specified (see 3.1). The quantity of thermoswitches shall be \ 1956 certified. When tested as specified in 4.7.4.9, battery voltage shall \ 1957 be zero volts after each high temperature storage and batteries shall \ 1958 meet the voltage requirement of 3.5.2 after cooling. After completion \ 1959 of the test the battery shall be able to meet the full discharge \ 1960 capacity requirement of 3.5.3 after full charge. | 1961 """ 1962 1963 failed_tests = [] 1964 timeout_seconds = 30 1965 1966 temperature = 75 1967 high_range = temperature + 5 1968 low_range = temperature - 5 1969 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {temperature}°C") 1970 _plateset.thermistor1 = _plateset.thermistor2 = temperature 1971 1972 time.sleep(1) 1973 serial_data = serial_monitor.read() # Get the serial data 1974 # Convert temperature to Celsius from Kelvin 1975 therm_one = serial_data["dk_temp"] / 10 - 273 1976 therm_two = serial_data["dk_temp1"] / 10 - 273 1977 temp_range_text = f"{temperature}°C +/- 5°C" 1978 1979 if low_range <= therm_one <= high_range: 1980 logger.write_result_to_html_report( 1981 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1982 ) 1983 else: 1984 logger.write_result_to_html_report( 1985 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1986 f"of {temp_range_text}</font>" 1987 ) 1988 failed_tests.append("THERM1") 1989 1990 if low_range <= therm_two <= high_range: 1991 logger.write_result_to_html_report( 1992 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1993 ) 1994 else: 1995 logger.write_result_to_html_report( 1996 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1997 f"expected range of {temp_range_text}</font>" 1998 ) 1999 failed_tests.append("THERM2") 2000 2001 start = time.perf_counter() 2002 while (serial_data := serial_monitor.read()) and not serial_data["flags.fault_overtemp_discharge"]: 2003 if time.perf_counter() - start > timeout_seconds: 2004 message = f"Over-temperature discharge flag was not raised after {timeout_seconds} seconds." 2005 logger.write_failure_to_html_report(message) 2006 failed_tests.append("Over-temperature discharge flag.") 2007 break 2008 else: 2009 logger.write_result_to_html_report("Over-temperature discharge flag was properly set.") 2010 2011 logger.write_info_to_report("Measuring voltage...") 2012 time.sleep(1) 2013 2014 low_range = -0.2 2015 high_range = 3 2016 volt_range_text = f"0V ({low_range}V / +{high_range}V)" 2017 2018 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2019 if not -0.2 <= _bms.dmm.volts <= 3: 2020 logger.write_failure_to_html_report( 2021 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2022 f"which was not within the expected range of {volt_range_text}" 2023 ) 2024 failed_tests.append("HITL Terminal Voltage") 2025 else: 2026 logger.write_result_to_html_report( 2027 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2028 f"which was within the expected range of {volt_range_text}" 2029 ) 2030 2031 logger.write_info_to_report("Attempting to charge at 1A") 2032 high_range = 0.020 2033 amp_range_text = "0A +/- 20mA" 2034 with _bms.charger(16.8, 1.0): 2035 time.sleep(1) 2036 if (high_range * -1) <= _bms.charger.amps <= high_range: 2037 logger.write_result_to_html_report( 2038 f"HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, which was within the " 2039 f"expected range of {amp_range_text}" 2040 ) 2041 else: 2042 logger.write_result_to_html_report( 2043 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, ' 2044 f"which was not within the expected range of {amp_range_text} </font>" 2045 ) 2046 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 2047 2048 logger.write_info_to_report("Attempting to discharge at 1A") 2049 2050 with _bms.load(1): 2051 time.sleep(1) 2052 if (high_range * -1) <= _bms.load.amps <= high_range: 2053 logger.write_result_to_html_report( 2054 f"HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, which was within the " 2055 f"expected range of {amp_range_text}" 2056 ) 2057 else: 2058 logger.write_result_to_html_report( 2059 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, ' 2060 f"which was not within the expected range of {amp_range_text} </font>" 2061 ) 2062 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2063 2064 temperature = 20 2065 logger.write_info_to_report(f"Setting Temperature to {temperature}°C") 2066 _plateset.thermistor1 = _plateset.thermistor2 = temperature 2067 2068 logger.write_info_to_report("Measuring voltage...") 2069 time.sleep(1) 2070 high_range = 17 2071 low_range = 9 2072 if not (_plateset.load_switch or _plateset.charger_switch): 2073 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2074 if not low_range <= _bms.dmm.volts <= high_range: 2075 logger.write_failure_to_html_report( 2076 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2077 f"which was not within the expected range of {low_range}V to {high_range}V" 2078 ) 2079 failed_tests.append("HITL Terminal Voltage") 2080 else: 2081 logger.write_result_to_html_report( 2082 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2083 f"which was within expected range of {low_range}V to {high_range}V" 2084 ) 2085 else: 2086 if not low_range <= _bms.dmm.volts <= high_range: 2087 logger.write_failure_to_html_report( 2088 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, which was not within the " 2089 f"expected range of {low_range}V to {high_range}V" 2090 ) 2091 failed_tests.append("HITL Terminal Voltage") 2092 else: 2093 logger.write_result_to_html_report( 2094 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2095 f"which was within expected range of {low_range}V to {high_range}V" 2096 ) 2097 2098 if len(failed_tests) > 0: 2099 pytest.fail() 2100 2101 logger.write_result_to_html_report("All checks passed test")
Run a test for High temperature temporary cutoff
1928 def test_high_temperature_temp_cutoff(self): 1929 """ 1930 | Description | High Temperature Temporary Cutoff | 1931 | :------------------- | :--------------------------------------------------------------------- | 1932 | GitHub Issue | turnaroundfactor/HITL#517 | 1933 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 1934jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D27) | 1935 | MIL-PRF Sections | 3.7.2.4 (High Temperature permanent cut off devices) | 1936 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 1937 2. Put cells in rested state at 4.2V per cell </br>\ 1938 3. Set THERM1 and THERM2 to 75°C </br>\ 1939 4. Measure Voltage </br>\ 1940 5. Attempt to charge at 1A </br>\ 1941 6. Attempt to discharge at 1A </br>\ 1942 7. Set temperature to 20°C </br>\ 1943 8. Measure Voltage | 1944 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 75°C +/- 5°C </br>\ 1945 ⦁ Expect Overtemp charge & Overtemp discharge flags to be set </br>\ 1946 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V </br>\ 1947 ⦁ HITL Terminal Current is 0A +/- 1mA after charging </br>\ 1948 ⦁ HITL Terminal Current is 0A +/- 1mA after discharging </br>\ 1949 ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V | 1950 | Estimated Duration | 12 seconds | 1951 | Note | Each battery shall contain a minimum quantity of normally closed \ 1952 thermoswitches that shall open at 158 ± 9°F (70 ± 5°C) and close at \ 1953 122 ± 9°F (50 ± 5°C). Each thermoswitch shall make physical contact \ 1954 with not less than one cell. The minimum quantity shall be as \ 1955 specified (see 3.1). The quantity of thermoswitches shall be \ 1956 certified. When tested as specified in 4.7.4.9, battery voltage shall \ 1957 be zero volts after each high temperature storage and batteries shall \ 1958 meet the voltage requirement of 3.5.2 after cooling. After completion \ 1959 of the test the battery shall be able to meet the full discharge \ 1960 capacity requirement of 3.5.3 after full charge. | 1961 """ 1962 1963 failed_tests = [] 1964 timeout_seconds = 30 1965 1966 temperature = 75 1967 high_range = temperature + 5 1968 low_range = temperature - 5 1969 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {temperature}°C") 1970 _plateset.thermistor1 = _plateset.thermistor2 = temperature 1971 1972 time.sleep(1) 1973 serial_data = serial_monitor.read() # Get the serial data 1974 # Convert temperature to Celsius from Kelvin 1975 therm_one = serial_data["dk_temp"] / 10 - 273 1976 therm_two = serial_data["dk_temp1"] / 10 - 273 1977 temp_range_text = f"{temperature}°C +/- 5°C" 1978 1979 if low_range <= therm_one <= high_range: 1980 logger.write_result_to_html_report( 1981 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1982 ) 1983 else: 1984 logger.write_result_to_html_report( 1985 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 1986 f"of {temp_range_text}</font>" 1987 ) 1988 failed_tests.append("THERM1") 1989 1990 if low_range <= therm_two <= high_range: 1991 logger.write_result_to_html_report( 1992 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range_text}" 1993 ) 1994 else: 1995 logger.write_result_to_html_report( 1996 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 1997 f"expected range of {temp_range_text}</font>" 1998 ) 1999 failed_tests.append("THERM2") 2000 2001 start = time.perf_counter() 2002 while (serial_data := serial_monitor.read()) and not serial_data["flags.fault_overtemp_discharge"]: 2003 if time.perf_counter() - start > timeout_seconds: 2004 message = f"Over-temperature discharge flag was not raised after {timeout_seconds} seconds." 2005 logger.write_failure_to_html_report(message) 2006 failed_tests.append("Over-temperature discharge flag.") 2007 break 2008 else: 2009 logger.write_result_to_html_report("Over-temperature discharge flag was properly set.") 2010 2011 logger.write_info_to_report("Measuring voltage...") 2012 time.sleep(1) 2013 2014 low_range = -0.2 2015 high_range = 3 2016 volt_range_text = f"0V ({low_range}V / +{high_range}V)" 2017 2018 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2019 if not -0.2 <= _bms.dmm.volts <= 3: 2020 logger.write_failure_to_html_report( 2021 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2022 f"which was not within the expected range of {volt_range_text}" 2023 ) 2024 failed_tests.append("HITL Terminal Voltage") 2025 else: 2026 logger.write_result_to_html_report( 2027 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2028 f"which was within the expected range of {volt_range_text}" 2029 ) 2030 2031 logger.write_info_to_report("Attempting to charge at 1A") 2032 high_range = 0.020 2033 amp_range_text = "0A +/- 20mA" 2034 with _bms.charger(16.8, 1.0): 2035 time.sleep(1) 2036 if (high_range * -1) <= _bms.charger.amps <= high_range: 2037 logger.write_result_to_html_report( 2038 f"HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, which was within the " 2039 f"expected range of {amp_range_text}" 2040 ) 2041 else: 2042 logger.write_result_to_html_report( 2043 f'<font color="#990000">HITL Terminal Current was {_bms.charger.amps:.3f}A when charging, ' 2044 f"which was not within the expected range of {amp_range_text} </font>" 2045 ) 2046 failed_tests.append("HITL Terminal Current after attempting to charge at 1A") 2047 2048 logger.write_info_to_report("Attempting to discharge at 1A") 2049 2050 with _bms.load(1): 2051 time.sleep(1) 2052 if (high_range * -1) <= _bms.load.amps <= high_range: 2053 logger.write_result_to_html_report( 2054 f"HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, which was within the " 2055 f"expected range of {amp_range_text}" 2056 ) 2057 else: 2058 logger.write_result_to_html_report( 2059 f'<font color="#990000">HITL Terminal Current was {_bms.load.amps:.3f}A when discharging, ' 2060 f"which was not within the expected range of {amp_range_text} </font>" 2061 ) 2062 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2063 2064 temperature = 20 2065 logger.write_info_to_report(f"Setting Temperature to {temperature}°C") 2066 _plateset.thermistor1 = _plateset.thermistor2 = temperature 2067 2068 logger.write_info_to_report("Measuring voltage...") 2069 time.sleep(1) 2070 high_range = 17 2071 low_range = 9 2072 if not (_plateset.load_switch or _plateset.charger_switch): 2073 with _bms.load(0.0, DischargeType.CONSTANT_RESISTANCE, 50_000): 2074 if not low_range <= _bms.dmm.volts <= high_range: 2075 logger.write_failure_to_html_report( 2076 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2077 f"which was not within the expected range of {low_range}V to {high_range}V" 2078 ) 2079 failed_tests.append("HITL Terminal Voltage") 2080 else: 2081 logger.write_result_to_html_report( 2082 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2083 f"which was within expected range of {low_range}V to {high_range}V" 2084 ) 2085 else: 2086 if not low_range <= _bms.dmm.volts <= high_range: 2087 logger.write_failure_to_html_report( 2088 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, which was not within the " 2089 f"expected range of {low_range}V to {high_range}V" 2090 ) 2091 failed_tests.append("HITL Terminal Voltage") 2092 else: 2093 logger.write_result_to_html_report( 2094 f"HITL Terminal Voltage was {_bms.dmm.volts:.3f}V, " 2095 f"which was within expected range of {low_range}V to {high_range}V" 2096 ) 2097 2098 if len(failed_tests) > 0: 2099 pytest.fail() 2100 2101 logger.write_result_to_html_report("All checks passed test")
| Description | High Temperature Temporary Cutoff |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#517 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.7.2.4 (High Temperature permanent cut off devices) |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 4.2V per cell 3. Set THERM1 and THERM2 to 75°C 4. Measure Voltage 5. Attempt to charge at 1A 6. Attempt to discharge at 1A 7. Set temperature to 20°C 8. Measure Voltage |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 75°C +/- 5°C ⦁ Expect Overtemp charge & Overtemp discharge flags to be set ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V ⦁ HITL Terminal Current is 0A +/- 1mA after charging ⦁ HITL Terminal Current is 0A +/- 1mA after discharging ⦁ HITL Terminal Voltage is 0V -0.2, +3.0 V |
| Estimated Duration | 12 seconds |
| Note | Each battery shall contain a minimum quantity of normally closed thermoswitches that shall open at 158 ± 9°F (70 ± 5°C) and close at 122 ± 9°F (50 ± 5°C). Each thermoswitch shall make physical contact with not less than one cell. The minimum quantity shall be as specified (see 3.1). The quantity of thermoswitches shall be certified. When tested as specified in 4.7.4.9, battery voltage shall be zero volts after each high temperature storage and batteries shall meet the voltage requirement of 3.5.2 after cooling. After completion of the test the battery shall be able to meet the full discharge capacity requirement of 3.5.3 after full charge. |
2104@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 2105class TestExtremeLowTempDischarge(CSVRecordEvent): 2106 """Run a test for extreme low temperature discharge""" 2107 2108 def test_extreme_low_temp_discharge(self): 2109 """ 2110 | Description | Extreme low temperature discharge | 2111 | :------------------- | :--------------------------------------------------------------------- | 2112 | GitHub Issue | turnaroundfactor/HITL#518 | 2113 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2114jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 2115 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature discharge) | 2116 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2117 2. Put cells in rested state at 4.2V per cell </br>\ 2118 3. Set THERM1 and THERM2 to -30°C </br>\ 2119 4. Attempt to discharge at 1A | 2120 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -30°C +/- 1.1°C </br>\ 2121 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2122 | Estimated Duration | 6 seconds | 2123 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2124 Charge batteries in accordance with 4.6; use of 4.6.3 is not permitted.\ 2125 Store the batteries at -22 ± 2°F (-30 ± 1.1°C) for a minimum of \ 2126 4 hours. Discharge under these conditions at the rate specified to the \ 2127 specified cutoff voltage (see 3.1). After testing, batteries shall \ 2128 meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2129 """ 2130 2131 failed_tests = [] 2132 2133 logger.write_info_to_report("Setting THERM1 & THERM2 to -30°C") 2134 _plateset.disengage_safety_protocols = True 2135 _plateset.thermistor1 = _plateset.thermistor2 = -30 2136 _plateset.disengage_safety_protocols = False 2137 2138 # Get the serial data 2139 serial_data = serial_monitor.read() 2140 2141 # Convert temperature to Celsius from Kelvin 2142 therm_one = serial_data["dk_temp"] / 10 - 273 2143 therm_two = serial_data["dk_temp1"] / 10 - 273 2144 temp_range = "-30°C +/- 1.1°C" 2145 2146 if -31.1 <= therm_one <= -28.9: 2147 logger.write_result_to_html_report( 2148 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2149 ) 2150 else: 2151 logger.write_result_to_html_report( 2152 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2153 f"of {temp_range}</font>" 2154 ) 2155 failed_tests.append("THERM1 after setting temperature to -30°C") 2156 2157 if -31.1 <= therm_two <= -28.9: 2158 logger.write_result_to_html_report( 2159 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2160 ) 2161 else: 2162 logger.write_result_to_html_report( 2163 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2164 f"expected range of {temp_range}</font>" 2165 ) 2166 failed_tests.append("THERM2 after setting temperature to -30°C") 2167 2168 logger.write_info_to_report("Attempting to discharge at 1A") 2169 2170 with _bms.load(1): 2171 time.sleep(1) 2172 load_amps = -1 * _bms.load.amps 2173 expected_current_range = "-1A +/- 30mA" 2174 if -1.03 <= load_amps <= -0.97: 2175 logger.write_result_to_html_report( 2176 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2177 f"expected range of {expected_current_range}" 2178 ) 2179 else: 2180 logger.write_result_to_html_report( 2181 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2182 f"which was not within the expected range of {expected_current_range} </font>" 2183 ) 2184 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2185 2186 if len(failed_tests) > 0: 2187 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2188 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2189 pytest.fail(message) 2190 2191 logger.write_result_to_html_report("All checks passed test")
Run a test for extreme low temperature discharge
2108 def test_extreme_low_temp_discharge(self): 2109 """ 2110 | Description | Extreme low temperature discharge | 2111 | :------------------- | :--------------------------------------------------------------------- | 2112 | GitHub Issue | turnaroundfactor/HITL#518 | 2113 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2114jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D28) | 2115 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature discharge) | 2116 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2117 2. Put cells in rested state at 4.2V per cell </br>\ 2118 3. Set THERM1 and THERM2 to -30°C </br>\ 2119 4. Attempt to discharge at 1A | 2120 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -30°C +/- 1.1°C </br>\ 2121 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2122 | Estimated Duration | 6 seconds | 2123 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2124 Charge batteries in accordance with 4.6; use of 4.6.3 is not permitted.\ 2125 Store the batteries at -22 ± 2°F (-30 ± 1.1°C) for a minimum of \ 2126 4 hours. Discharge under these conditions at the rate specified to the \ 2127 specified cutoff voltage (see 3.1). After testing, batteries shall \ 2128 meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2129 """ 2130 2131 failed_tests = [] 2132 2133 logger.write_info_to_report("Setting THERM1 & THERM2 to -30°C") 2134 _plateset.disengage_safety_protocols = True 2135 _plateset.thermistor1 = _plateset.thermistor2 = -30 2136 _plateset.disengage_safety_protocols = False 2137 2138 # Get the serial data 2139 serial_data = serial_monitor.read() 2140 2141 # Convert temperature to Celsius from Kelvin 2142 therm_one = serial_data["dk_temp"] / 10 - 273 2143 therm_two = serial_data["dk_temp1"] / 10 - 273 2144 temp_range = "-30°C +/- 1.1°C" 2145 2146 if -31.1 <= therm_one <= -28.9: 2147 logger.write_result_to_html_report( 2148 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2149 ) 2150 else: 2151 logger.write_result_to_html_report( 2152 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2153 f"of {temp_range}</font>" 2154 ) 2155 failed_tests.append("THERM1 after setting temperature to -30°C") 2156 2157 if -31.1 <= therm_two <= -28.9: 2158 logger.write_result_to_html_report( 2159 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2160 ) 2161 else: 2162 logger.write_result_to_html_report( 2163 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2164 f"expected range of {temp_range}</font>" 2165 ) 2166 failed_tests.append("THERM2 after setting temperature to -30°C") 2167 2168 logger.write_info_to_report("Attempting to discharge at 1A") 2169 2170 with _bms.load(1): 2171 time.sleep(1) 2172 load_amps = -1 * _bms.load.amps 2173 expected_current_range = "-1A +/- 30mA" 2174 if -1.03 <= load_amps <= -0.97: 2175 logger.write_result_to_html_report( 2176 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2177 f"expected range of {expected_current_range}" 2178 ) 2179 else: 2180 logger.write_result_to_html_report( 2181 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2182 f"which was not within the expected range of {expected_current_range} </font>" 2183 ) 2184 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2185 2186 if len(failed_tests) > 0: 2187 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2188 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2189 pytest.fail(message) 2190 2191 logger.write_result_to_html_report("All checks passed test")
| Description | Extreme low temperature discharge |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#518 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.3.2 (Extreme low temperature discharge) |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 4.2V per cell 3. Set THERM1 and THERM2 to -30°C 4. Attempt to discharge at 1A |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be -30°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be -1A +/- 30mA |
| Estimated Duration | 6 seconds |
| Note | For Type III (Li-ion) batteries the following test shall be performed. Charge batteries in accordance with 4.6; use of 4.6.3 is not permitted. Store the batteries at -22 ± 2°F (-30 ± 1.1°C) for a minimum of 4 hours. Discharge under these conditions at the rate specified to the specified cutoff voltage (see 3.1). After testing, batteries shall meet the requirements of 3.5.3, 3.6, and 3.6.1. |
2194@pytest.mark.parametrize("reset_test_environment", [{"volts": 4}], indirect=True) 2195class TestExtremeHighTempDischarge(CSVRecordEvent): 2196 """Run a test for extreme high temperature discharge""" 2197 2198 def test_extreme_high_temp_discharge(self): 2199 """ 2200 | Description | Extreme high temperature discharge | 2201 | :------------------- | :--------------------------------------------------------------------- | 2202 | GitHub Issue | turnaroundfactor/HITL#519 | 2203 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2204jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D31) | 2205 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2206 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2207 2. Put cells in rested state at 4.2V per cell </br>\ 2208 3. Set THERM1 and THERM2 to 55°C </br>\ 2209 4. Attempt to discharge at 1A | 2210 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 55°C +/- 1.1°C </br>\ 2211 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2212 | Estimated Duration | 6 seconds | 2213 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2214 Charge batteries in accordance with 4.6; use of 4.6.3 is not \ 2215 permitted. Store the batteries at 131 ± 2°F (55 ± 1.1°C) for a \ 2216 minimum of 4 hours. Discharge under these conditions at the rate \ 2217 specified to the specified cutoff voltage (see 3.1). After testing, \ 2218 batteries shall meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2219 """ 2220 2221 failed_tests = [] 2222 set_temp = 55 2223 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 2224 _plateset.disengage_safety_protocols = True 2225 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 2226 _plateset.disengage_safety_protocols = False 2227 2228 # Get the serial data 2229 serial_data = serial_monitor.read() 2230 2231 # Convert temperature to Celsius from Kelvin 2232 therm_one = serial_data["dk_temp"] / 10 - 273 2233 therm_two = serial_data["dk_temp1"] / 10 - 273 2234 temp_range = f"{set_temp}°C +/- 1.1°C" 2235 low_range = set_temp - 1.1 2236 high_range = set_temp + 1.1 2237 2238 if low_range <= therm_one <= high_range: 2239 logger.write_result_to_html_report( 2240 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2241 ) 2242 else: 2243 logger.write_result_to_html_report( 2244 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2245 f"of {temp_range}</font>" 2246 ) 2247 failed_tests.append(f"THERM1 after setting temperature to {set_temp}°C") 2248 2249 if low_range <= therm_two <= high_range: 2250 logger.write_result_to_html_report( 2251 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2252 ) 2253 else: 2254 logger.write_result_to_html_report( 2255 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2256 f"expected range of {temp_range}</font>" 2257 ) 2258 failed_tests.append(f"THERM2 after setting temperature to {set_temp}°C") 2259 2260 logger.write_info_to_report("Attempting to discharge at 1A") 2261 2262 with _bms.load(1): 2263 time.sleep(1) 2264 load_amps = -1 * _bms.load.amps 2265 low_range = -1 - 0.03 2266 high_range = -1 + 0.03 2267 expected_current_range = "-1A +/- 30mA" 2268 if low_range <= load_amps <= high_range: 2269 logger.write_result_to_html_report( 2270 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2271 f"expected range of {expected_current_range}" 2272 ) 2273 else: 2274 logger.write_result_to_html_report( 2275 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2276 f"which was not within the expected range of {expected_current_range} </font>" 2277 ) 2278 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2279 2280 if len(failed_tests) > 0: 2281 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2282 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2283 pytest.fail(message) 2284 2285 logger.write_result_to_html_report("All checks passed test")
Run a test for extreme high temperature discharge
2198 def test_extreme_high_temp_discharge(self): 2199 """ 2200 | Description | Extreme high temperature discharge | 2201 | :------------------- | :--------------------------------------------------------------------- | 2202 | GitHub Issue | turnaroundfactor/HITL#519 | 2203 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2204jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D31) | 2205 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2206 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 2207 2. Put cells in rested state at 4.2V per cell </br>\ 2208 3. Set THERM1 and THERM2 to 55°C </br>\ 2209 4. Attempt to discharge at 1A | 2210 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 55°C +/- 1.1°C </br>\ 2211 ⦁ Expect HITL Terminal Current to be -1A +/- 30mA | 2212 | Estimated Duration | 6 seconds | 2213 | Note | For Type III (Li-ion) batteries the following test shall be performed. \ 2214 Charge batteries in accordance with 4.6; use of 4.6.3 is not \ 2215 permitted. Store the batteries at 131 ± 2°F (55 ± 1.1°C) for a \ 2216 minimum of 4 hours. Discharge under these conditions at the rate \ 2217 specified to the specified cutoff voltage (see 3.1). After testing, \ 2218 batteries shall meet the requirements of 3.5.3, 3.6, and 3.6.1. | 2219 """ 2220 2221 failed_tests = [] 2222 set_temp = 55 2223 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 2224 _plateset.disengage_safety_protocols = True 2225 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 2226 _plateset.disengage_safety_protocols = False 2227 2228 # Get the serial data 2229 serial_data = serial_monitor.read() 2230 2231 # Convert temperature to Celsius from Kelvin 2232 therm_one = serial_data["dk_temp"] / 10 - 273 2233 therm_two = serial_data["dk_temp1"] / 10 - 273 2234 temp_range = f"{set_temp}°C +/- 1.1°C" 2235 low_range = set_temp - 1.1 2236 high_range = set_temp + 1.1 2237 2238 if low_range <= therm_one <= high_range: 2239 logger.write_result_to_html_report( 2240 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 2241 ) 2242 else: 2243 logger.write_result_to_html_report( 2244 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 2245 f"of {temp_range}</font>" 2246 ) 2247 failed_tests.append(f"THERM1 after setting temperature to {set_temp}°C") 2248 2249 if low_range <= therm_two <= high_range: 2250 logger.write_result_to_html_report( 2251 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 2252 ) 2253 else: 2254 logger.write_result_to_html_report( 2255 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 2256 f"expected range of {temp_range}</font>" 2257 ) 2258 failed_tests.append(f"THERM2 after setting temperature to {set_temp}°C") 2259 2260 logger.write_info_to_report("Attempting to discharge at 1A") 2261 2262 with _bms.load(1): 2263 time.sleep(1) 2264 load_amps = -1 * _bms.load.amps 2265 low_range = -1 - 0.03 2266 high_range = -1 + 0.03 2267 expected_current_range = "-1A +/- 30mA" 2268 if low_range <= load_amps <= high_range: 2269 logger.write_result_to_html_report( 2270 f"HITL Terminal Current was {load_amps:.3f}A, which was within the " 2271 f"expected range of {expected_current_range}" 2272 ) 2273 else: 2274 logger.write_result_to_html_report( 2275 f'<font color="#990000">HITL Terminal Current was {load_amps:.3f}A, ' 2276 f"which was not within the expected range of {expected_current_range} </font>" 2277 ) 2278 failed_tests.append("HITL Terminal Current after attempting to discharge at 1A") 2279 2280 if len(failed_tests) > 0: 2281 message = f"Overall, the following checks failed: {', '.join(failed_tests)}" 2282 logger.write_result_to_html_report(f'<font color="#990000">{message}</font>') 2283 pytest.fail(message) 2284 2285 logger.write_result_to_html_report("All checks passed test")
| Description | Extreme high temperature discharge |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#519 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 4.2V per cell 3. Set THERM1 and THERM2 to 55°C 4. Attempt to discharge at 1A |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 55°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be -1A +/- 30mA |
| Estimated Duration | 6 seconds |
| Note | For Type III (Li-ion) batteries the following test shall be performed. Charge batteries in accordance with 4.6; use of 4.6.3 is not permitted. Store the batteries at 131 ± 2°F (55 ± 1.1°C) for a minimum of 4 hours. Discharge under these conditions at the rate specified to the specified cutoff voltage (see 3.1). After testing, batteries shall meet the requirements of 3.5.3, 3.6, and 3.6.1. |
2288class TestStateTransitions: 2289 """Run a test for state transition""" 2290 2291 def test_fast_sample(self, serial_watcher: SerialWatcher): 2292 """ 2293 | Description | Enter and exit fast sample state | 2294 | :------------------- | :--------------------------------------------------------------------- | 2295 | GitHub Issue | turnaroundfactor/HITL#478 | 2296 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2297jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2298 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2299 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2300 2. Transition to fast sample by charging 50mA+ </br>\ 2301 3. Maintain 50mA+ and ensure we are still in fast sample after 5 minutes </br> 2302 4. Transition to slow sample by discharging less than -50mA </br>\ 2303 5. Transition back to fast sample by charging 50mA+ </br>\ 2304 6. Transition to deep slumber by charging/discharging in the range -50mA to 50mA </br> 2305 7. Transition back to fast sample by charging 50mA+ | 2306 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2307 | Estimated Duration | 5 minutes | 2308 | Note | The power consumption for all electronics within the battery shall \ 2309 be less than 350 micro-ampere average, per battery or independent \ 2310 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2311 """ 2312 logger.write_info_to_report("Testing Fast Sample") 2313 assert _bms.load and _bms.charger # Make sure hardware exists 2314 2315 logger.write_result_to_html_report("Slow sample") 2316 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2317 2318 logger.write_result_to_html_report("Slow sample -> Fast sample") 2319 with _bms.charger(16.8, 0.200): 2320 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2321 logger.write_result_to_html_report("Fast sample after 5 minutes") 2322 time.sleep(5.5 * 60) 2323 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2324 2325 logger.write_result_to_html_report("Fast sample -> Slow sample") 2326 with _bms.load(0.200): 2327 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2328 2329 logger.write_result_to_html_report("Slow sample -> Fast sample") 2330 with _bms.charger(16.8, 0.200): 2331 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2332 2333 logger.write_result_to_html_report("Fast sample -> Deep slumber") 2334 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=15 * 60) 2335 2336 def test_slow_sample(self, serial_watcher: SerialWatcher): 2337 """ 2338 | Description | Enter and exit slow sample state | 2339 | :------------------- | :--------------------------------------------------------------------- | 2340 | GitHub Issue | turnaroundfactor/HITL#478 | 2341 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2342jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2343 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2344 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2345 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2346 3. Transition back to slow sample by discharging less than -50mA </br>\ 2347 4. Test deep slumber timer (discharge) </br>\ 2348 5. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2349 6. Discharge less than -50mA for 2 minutes </br>\ 2350 7. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2351 20mA, ensuring it takes 5 minutes </br>\ 2352 8. Test deep slumber timer (charge) </br>\ 2353 9. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2354 10. Charge in the range 20mA to 50mA for 2 minutes (keeps the comparator on <\br>\ 2355 without entering fast sample) </br>\ 2356 11. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2357 20mA, ensuring it takes 5 minutes | 2358 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2359 | Estimated Duration | 5 minutes | 2360 | Note | The power consumption for all electronics within the battery shall \ 2361 be less than 350 micro-ampere average, per battery or independent \ 2362 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2363 """ 2364 logger.write_info_to_report("Testing Slow Sample") 2365 assert _bms.load and _bms.charger # Make sure hardware exists 2366 2367 logger.write_result_to_html_report("Slow sample") 2368 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2369 2370 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2371 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2372 2373 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2374 with _bms.load(0.200): 2375 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2376 2377 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (discharging)") 2378 serial_watcher.assert_measurements() 2379 time.sleep(2 * 60) 2380 with _bms.load(0.200): 2381 time.sleep(2 * 60) 2382 wait_start_time = time.perf_counter() 2383 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2384 if state_events := serial_watcher.events.get("BMS_State"): 2385 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2386 assert state_events[-1].time - wait_start_time >= 5 * 60 2387 2388 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2389 with _bms.load(0.200): 2390 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2391 2392 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (charging)") 2393 serial_watcher.assert_measurements() 2394 time.sleep(2 * 60) 2395 with _bms.charger(16.8, 0.040): 2396 time.sleep(2 * 60) 2397 wait_start_time = time.perf_counter() 2398 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2399 if state_events := serial_watcher.events.get("BMS_State"): 2400 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2401 assert state_events[-1].time - wait_start_time >= 5 * 60 2402 2403 logger.write_result_to_html_report("Slow sample state test passed") 2404 2405 def test_deep_slumber(self, serial_watcher: SerialWatcher): 2406 """ 2407 | Description | Enter and exit slow deep slumber state | 2408 | :------------------- | :--------------------------------------------------------------------- | 2409 | GitHub Issue | turnaroundfactor/HITL#478 | 2410 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2411jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2412 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2413 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2414 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2415 3. Transition back to slow sample by discharging less than -50mA </br>\ 2416 4. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2417 5. Transition back to slow sample by charging in the range 20mA to 40mA </br>\ 2418 6. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2419 7. Transition back to slow sample by setting charge enable low while resting | 2420 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2421 | Estimated Duration | 5 minutes | 2422 | Note | The power consumption for all electronics within the battery shall \ 2423 be less than 350 micro-ampere average, per battery or independent \ 2424 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2425 """ 2426 logger.write_info_to_report("Testing Deep slumber") 2427 assert _bms.load and _bms.charger # Make sure hardware exists 2428 2429 logger.write_result_to_html_report("Slow sample") 2430 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE, wait_time=5 * 60) 2431 2432 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2433 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2434 2435 logger.write_result_to_html_report("Deep slumber -> Slow sample (discharging)") 2436 with _bms.load(0.200): 2437 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2438 2439 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2440 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2441 2442 logger.write_result_to_html_report("Deep slumber -> Slow sample (charging)") 2443 with _bms.charger(16.8, 0.040): 2444 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2445 2446 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2447 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2448 2449 logger.write_result_to_html_report("Deep slumber -> Slow sample (CE pin)") 2450 _plateset.ce_switch = True 2451 try: 2452 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2453 finally: # Ensure ce pin is reset regardless of outcome 2454 _plateset.ce_switch = False 2455 2456 logger.write_result_to_html_report("Deep slumber state test passed")
Run a test for state transition
2291 def test_fast_sample(self, serial_watcher: SerialWatcher): 2292 """ 2293 | Description | Enter and exit fast sample state | 2294 | :------------------- | :--------------------------------------------------------------------- | 2295 | GitHub Issue | turnaroundfactor/HITL#478 | 2296 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2297jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2298 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2299 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2300 2. Transition to fast sample by charging 50mA+ </br>\ 2301 3. Maintain 50mA+ and ensure we are still in fast sample after 5 minutes </br> 2302 4. Transition to slow sample by discharging less than -50mA </br>\ 2303 5. Transition back to fast sample by charging 50mA+ </br>\ 2304 6. Transition to deep slumber by charging/discharging in the range -50mA to 50mA </br> 2305 7. Transition back to fast sample by charging 50mA+ | 2306 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2307 | Estimated Duration | 5 minutes | 2308 | Note | The power consumption for all electronics within the battery shall \ 2309 be less than 350 micro-ampere average, per battery or independent \ 2310 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2311 """ 2312 logger.write_info_to_report("Testing Fast Sample") 2313 assert _bms.load and _bms.charger # Make sure hardware exists 2314 2315 logger.write_result_to_html_report("Slow sample") 2316 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2317 2318 logger.write_result_to_html_report("Slow sample -> Fast sample") 2319 with _bms.charger(16.8, 0.200): 2320 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2321 logger.write_result_to_html_report("Fast sample after 5 minutes") 2322 time.sleep(5.5 * 60) 2323 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2324 2325 logger.write_result_to_html_report("Fast sample -> Slow sample") 2326 with _bms.load(0.200): 2327 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2328 2329 logger.write_result_to_html_report("Slow sample -> Fast sample") 2330 with _bms.charger(16.8, 0.200): 2331 serial_watcher.assert_true("BMS_State", BMSState.FAST_SAMPLE) 2332 2333 logger.write_result_to_html_report("Fast sample -> Deep slumber") 2334 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=15 * 60)
| Description | Enter and exit fast sample state |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#478 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.7 (Power consumption) |
| Instructions | 1. Ensure that we are in slow sample (the default state) 2. Transition to fast sample by charging 50mA+ 3. Maintain 50mA+ and ensure we are still in fast sample after 5 minutes |
4. Transition to slow sample by discharging less than -50mA 5. Transition back to fast sample by charging 50mA+ 6. Transition to deep slumber by charging/discharging in the range -50mA to 50mA 7. Transition back to fast sample by charging 50mA+ | | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | | Estimated Duration | 5 minutes | | Note | The power consumption for all electronics within the battery shall be less than 350 micro-ampere average, per battery or independent section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). |
2336 def test_slow_sample(self, serial_watcher: SerialWatcher): 2337 """ 2338 | Description | Enter and exit slow sample state | 2339 | :------------------- | :--------------------------------------------------------------------- | 2340 | GitHub Issue | turnaroundfactor/HITL#478 | 2341 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2342jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2343 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2344 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2345 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2346 3. Transition back to slow sample by discharging less than -50mA </br>\ 2347 4. Test deep slumber timer (discharge) </br>\ 2348 5. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2349 6. Discharge less than -50mA for 2 minutes </br>\ 2350 7. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2351 20mA, ensuring it takes 5 minutes </br>\ 2352 8. Test deep slumber timer (charge) </br>\ 2353 9. Charge/discharge in the range -46mA to 20mA for 2 minutes </br>\ 2354 10. Charge in the range 20mA to 50mA for 2 minutes (keeps the comparator on <\br>\ 2355 without entering fast sample) </br>\ 2356 11. Transition to deep slumber by charging/discharging in the range -46mA to </br>\ 2357 20mA, ensuring it takes 5 minutes | 2358 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2359 | Estimated Duration | 5 minutes | 2360 | Note | The power consumption for all electronics within the battery shall \ 2361 be less than 350 micro-ampere average, per battery or independent \ 2362 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2363 """ 2364 logger.write_info_to_report("Testing Slow Sample") 2365 assert _bms.load and _bms.charger # Make sure hardware exists 2366 2367 logger.write_result_to_html_report("Slow sample") 2368 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2369 2370 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2371 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2372 2373 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2374 with _bms.load(0.200): 2375 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2376 2377 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (discharging)") 2378 serial_watcher.assert_measurements() 2379 time.sleep(2 * 60) 2380 with _bms.load(0.200): 2381 time.sleep(2 * 60) 2382 wait_start_time = time.perf_counter() 2383 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2384 if state_events := serial_watcher.events.get("BMS_State"): 2385 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2386 assert state_events[-1].time - wait_start_time >= 5 * 60 2387 2388 logger.write_result_to_html_report("Deep slumber -> Slow sample") 2389 with _bms.load(0.200): 2390 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2391 2392 logger.write_result_to_html_report("Slow sample -> Deep slumber after 5 minutes (charging)") 2393 serial_watcher.assert_measurements() 2394 time.sleep(2 * 60) 2395 with _bms.charger(16.8, 0.040): 2396 time.sleep(2 * 60) 2397 wait_start_time = time.perf_counter() 2398 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2399 if state_events := serial_watcher.events.get("BMS_State"): 2400 logger.write_result_to_html_report(f"Receive time: {state_events[-1].time - wait_start_time:.6f} seconds") 2401 assert state_events[-1].time - wait_start_time >= 5 * 60 2402 2403 logger.write_result_to_html_report("Slow sample state test passed")
| Description | Enter and exit slow sample state |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#478 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.7 (Power consumption) |
| Instructions | 1. Ensure that we are in slow sample (the default state) 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA 3. Transition back to slow sample by discharging less than -50mA 4. Test deep slumber timer (discharge) 5. Charge/discharge in the range -46mA to 20mA for 2 minutes 6. Discharge less than -50mA for 2 minutes 7. Transition to deep slumber by charging/discharging in the range -46mA to 20mA, ensuring it takes 5 minutes 8. Test deep slumber timer (charge) 9. Charge/discharge in the range -46mA to 20mA for 2 minutes 10. Charge in the range 20mA to 50mA for 2 minutes (keeps the comparator on <r> without entering fast sample) 11. Transition to deep slumber by charging/discharging in the range -46mA to 20mA, ensuring it takes 5 minutes |
| Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state |
| Estimated Duration | 5 minutes |
| Note | The power consumption for all electronics within the battery shall be less than 350 micro-ampere average, per battery or independent section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). |
2405 def test_deep_slumber(self, serial_watcher: SerialWatcher): 2406 """ 2407 | Description | Enter and exit slow deep slumber state | 2408 | :------------------- | :--------------------------------------------------------------------- | 2409 | GitHub Issue | turnaroundfactor/HITL#478 | 2410 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2411jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D10) | 2412 | MIL-PRF Sections | 3.5.7 (Power consumption) | 2413 | Instructions | 1. Ensure that we are in slow sample (the default state) </br>\ 2414 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2415 3. Transition back to slow sample by discharging less than -50mA </br>\ 2416 4. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2417 5. Transition back to slow sample by charging in the range 20mA to 40mA </br>\ 2418 6. Transition to deep slumber by charging/discharging in the range -46mA to 20mA </br>\ 2419 7. Transition back to slow sample by setting charge enable low while resting | 2420 | Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state | 2421 | Estimated Duration | 5 minutes | 2422 | Note | The power consumption for all electronics within the battery shall \ 2423 be less than 350 micro-ampere average, per battery or independent \ 2424 section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). | 2425 """ 2426 logger.write_info_to_report("Testing Deep slumber") 2427 assert _bms.load and _bms.charger # Make sure hardware exists 2428 2429 logger.write_result_to_html_report("Slow sample") 2430 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE, wait_time=5 * 60) 2431 2432 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2433 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2434 2435 logger.write_result_to_html_report("Deep slumber -> Slow sample (discharging)") 2436 with _bms.load(0.200): 2437 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2438 2439 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2440 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2441 2442 logger.write_result_to_html_report("Deep slumber -> Slow sample (charging)") 2443 with _bms.charger(16.8, 0.040): 2444 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2445 2446 logger.write_result_to_html_report("Slow sample -> Deep slumber") 2447 serial_watcher.assert_true("BMS_State", BMSState.DEEP_SLUMBER, wait_time=10 * 60) 2448 2449 logger.write_result_to_html_report("Deep slumber -> Slow sample (CE pin)") 2450 _plateset.ce_switch = True 2451 try: 2452 serial_watcher.assert_true("BMS_State", BMSState.SLOW_SAMPLE) 2453 finally: # Ensure ce pin is reset regardless of outcome 2454 _plateset.ce_switch = False 2455 2456 logger.write_result_to_html_report("Deep slumber state test passed")
| Description | Enter and exit slow deep slumber state |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#478 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.7 (Power consumption) |
| Instructions | 1. Ensure that we are in slow sample (the default state) 2. Transition to deep slumber by charging/discharging in the range -46mA to 20mA 3. Transition back to slow sample by discharging less than -50mA 4. Transition to deep slumber by charging/discharging in the range -46mA to 20mA 5. Transition back to slow sample by charging in the range 20mA to 40mA 6. Transition to deep slumber by charging/discharging in the range -46mA to 20mA 7. Transition back to slow sample by setting charge enable low while resting |
| Pass / Fail Criteria | ⦁ Fail if we are unable to transition to the desired state |
| Estimated Duration | 5 minutes |
| Note | The power consumption for all electronics within the battery shall be less than 350 micro-ampere average, per battery or independent section where applicable, at 68 ± 5°F (20 ± 2.8°C) (See 4.7.2.13). |
2459@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 2460class TestVoltageAccuracy: 2461 """Run a test for voltage accuracy""" 2462 2463 class VoltageCellAccuracy(CSVRecordEvent): 2464 """@private Check if HITL cell sim voltage matches serial cell sim voltage within 1%""" 2465 2466 percent_closeness = 0.05 2467 max = SimpleNamespace(cell_id=0, sim_v=0, bms_v=0, error=0.0) 2468 2469 @classmethod 2470 def failed(cls) -> bool: 2471 """Check if test parameters were exceeded.""" 2472 return bool(cls.max.error > cls.percent_closeness) 2473 2474 @classmethod 2475 def verify(cls, row, serial_data, _cell_data): 2476 """Cell voltage within range""" 2477 for i, cell_id in enumerate(_bms.cells): 2478 row_data = SimpleNamespace( 2479 cell_id=cell_id, 2480 sim_v=row[f"ADC Plate Cell {cell_id} Voltage (V)"], 2481 bms_v=serial_data[f"mvolt_cell{'' if i == 0 else i}"] / 1000, 2482 ) 2483 row_data.error = abs((row_data.bms_v - row_data.sim_v) / row_data.sim_v) 2484 cls.max = max(cls.max, row_data, key=lambda data: data.error) 2485 2486 @classmethod 2487 def result(cls): 2488 """Detailed test result information.""" 2489 return ( 2490 f"Cell Voltage Error: {cls.cmp(cls.max.error, '<=', cls.percent_closeness)}" 2491 f"(Sim {cls.max.cell_id}: {cls.max.sim_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 2492 ) 2493 2494 class TerminalVoltageAccuracy(CSVRecordEvent): 2495 """@private Compare HITL voltage to reported Terminal voltage.""" 2496 2497 percent_closeness = 0.05 2498 max = SimpleNamespace(hitl_v=0, bms_v=0, error=0.0) 2499 2500 @classmethod 2501 def failed(cls) -> bool: 2502 """Check if test parameters were exceeded.""" 2503 return bool(cls.max.error > cls.percent_closeness) 2504 2505 @classmethod 2506 def verify(cls, row, serial_data, _cell_data): 2507 """Terminal voltage within range""" 2508 row_data = SimpleNamespace(hitl_v=row["HITL Voltage (V)"], bms_v=serial_data["mvolt_terminal"] / 1000) 2509 row_data.error = abs((row_data.bms_v - row_data.hitl_v) / row_data.hitl_v) 2510 cls.max = max(cls.max, row_data, key=lambda data: data.error) 2511 2512 @classmethod 2513 def result(cls): 2514 """Detailed test result information.""" 2515 return ( 2516 f"Terminal Voltage error: {cls.cmp(cls.max.error, '<=', cls.percent_closeness)} " 2517 f"(HITL: {cls.max.hitl_v * 1000:.1f} mv, BMS: {cls.max.bms_v * 1000:.1f} mv)" 2518 ) 2519 2520 def test_voltage_accuracy(self): 2521 """ 2522 | Description | Test the cell voltage accuracy | 2523 | :------------------- | :--------------------------------------------------------------------- | 2524 | GitHub Issue | turnaroundfactor/HITL#399 | 2525 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 2526 4.7.2.14.3 (Accuracy During Discharge) | 2527 | Instructions | 1. Set thermistors to 23C </br>\ 2528 2. Put cells in a rested state at 2.5V per cell </br>\ 2529 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 2530 4. Increase cell voltages in 100 mV increments up to and including 4.2V </br>\ 2531 | Pass / Fail Criteria | ⦁ HITL cell sim voltage matches serial cell sim voltage to within 1% </br>\ 2532 ⦁ HITL voltage matches serial Terminal voltage to within 1% </br>\ 2533 | Estimated Duration | 12 hours | 2534 | Note | MIL-PRF 3.5.8.3 (Accuracy): The values of the display shall be \ 2535 accurate within +0/-5% of the actual values for the battery. \ 2536 (see 4.7.2.14.3). | 2537 """ 2538 _bms.timer.reset() # Keep track of runtime 2539 for target_mv in range(2500, 4300, 100): 2540 voltages = ", ".join(f"{cell.volts}V" for cell in _bms.cells.values()) 2541 logger.write_info_to_report(f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {voltages}, ") 2542 _bms.csv.cycle.record(_bms.timer.elapsed_time) 2543 for cell in _bms.cells.values(): 2544 cell.volts = target_mv / 1000 2545 time.sleep(3) 2546 2547 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 2548 pytest.fail(CSVRecordEvent.result())
Run a test for voltage accuracy
2520 def test_voltage_accuracy(self): 2521 """ 2522 | Description | Test the cell voltage accuracy | 2523 | :------------------- | :--------------------------------------------------------------------- | 2524 | GitHub Issue | turnaroundfactor/HITL#399 | 2525 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 2526 4.7.2.14.3 (Accuracy During Discharge) | 2527 | Instructions | 1. Set thermistors to 23C </br>\ 2528 2. Put cells in a rested state at 2.5V per cell </br>\ 2529 3. Charge battery (16.8V / 3A / 100 mA cutoff) </br>\ 2530 4. Increase cell voltages in 100 mV increments up to and including 4.2V </br>\ 2531 | Pass / Fail Criteria | ⦁ HITL cell sim voltage matches serial cell sim voltage to within 1% </br>\ 2532 ⦁ HITL voltage matches serial Terminal voltage to within 1% </br>\ 2533 | Estimated Duration | 12 hours | 2534 | Note | MIL-PRF 3.5.8.3 (Accuracy): The values of the display shall be \ 2535 accurate within +0/-5% of the actual values for the battery. \ 2536 (see 4.7.2.14.3). | 2537 """ 2538 _bms.timer.reset() # Keep track of runtime 2539 for target_mv in range(2500, 4300, 100): 2540 voltages = ", ".join(f"{cell.volts}V" for cell in _bms.cells.values()) 2541 logger.write_info_to_report(f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Voltage: {voltages}, ") 2542 _bms.csv.cycle.record(_bms.timer.elapsed_time) 2543 for cell in _bms.cells.values(): 2544 cell.volts = target_mv / 1000 2545 time.sleep(3) 2546 2547 if CSVRecordEvent.failed(): # FIXME(JA): make this implicit? 2548 pytest.fail(CSVRecordEvent.result())
| Description | Test the cell voltage accuracy | ||||
|---|---|---|---|---|---|
| GitHub Issue | turnaroundfactor/HITL#399 | ||||
| MIL-PRF Sections | 3.5.8.3 (Accuracy) 4.7.2.14.3 (Accuracy During Discharge) | ||||
| Instructions | 1. Set thermistors to 23C 2. Put cells in a rested state at 2.5V per cell 3. Charge battery (16.8V / 3A / 100 mA cutoff) 4. Increase cell voltages in 100 mV increments up to and including 4.2V | Pass / Fail Criteria | ⦁ HITL cell sim voltage matches serial cell sim voltage to within 1% ⦁ HITL voltage matches serial Terminal voltage to within 1% | Estimated Duration | 12 hours |
| Note | MIL-PRF 3.5.8.3 (Accuracy): The values of the display shall be accurate within +0/-5% of the actual values for the battery. (see 4.7.2.14.3). |
2552@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.5}], indirect=True) 2553class TestSerialFaults: 2554 """Test all faults""" 2555 2556 def test_wakeup_1(self, serial_watcher: SerialWatcher): 2557 """ 2558 | Description | Test Wakeup 1 | 2559 | :------------------- | :--------------------------------------------------------------------- | 2560 | GitHub Issue | turnaroundfactor/HITL#476 | 2561 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2562jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2563 | MIL-PRF Sections | 3.6.8.2 (Battery wake up) | 2564 | Instructions | 1. Set Default state at 0mA </br>\ 2565 2. Discharge at 100 mA (below -46 mA) </br>\ 2566 3. Discharge at 10 mA (between -46mA and 19ma) </br>\ 2567 4. Charge at 100 mA, (above 19 mA) | 2568 | Pass / Fail Criteria | ⦁ n_wakeup_gpio = 1, with default state at 0 mA </br>\ 2569 ⦁ n_wakeup_gpio = 0, with default state at 100 mA </br>\ 2570 ⦁ n_wakeup_gpio = 1, with default state at 10 mA </br>\ 2571 ⦁ n_wakeup_gpio = 0, with default state at 0 mA | 2572 | Estimated Duration | 10 seconds | 2573 | Note | The BMS should be in a state of slumber if the current measured is \ 2574 between -46mA and 19ma. This is done using internal comparators on the \ 2575 board to a logical AND chip feeding into an interrupt pin. To test \ 2576 this, 3 different currents should be set. One current below -46ma, \ 2577 another current between -46mA and 19ma, and another current above \ 2578 19ma. If the current is within the allowable range, we should read \ 2579 logic 1 on the N_WAKEUP pin. If the current is outside (above or \ 2580 below) we should read logic 0 on the pin. | 2581 """ 2582 2583 logger.write_info_to_report("Testing Wakeup") 2584 assert _bms.load and _bms.charger # Confirm hardware is available 2585 2586 logger.write_result_to_html_report("Default state") 2587 serial_watcher.assert_true("n_wakeup_gpio", True, 1) 2588 2589 logger.write_result_to_html_report("Discharging 200 mA") 2590 with _bms.load(0.200): 2591 serial_watcher.assert_true("n_wakeup_gpio", False, 2) 2592 2593 logger.write_result_to_html_report("Discharging 10 mA") 2594 with _bms.load(0.010): 2595 serial_watcher.assert_true("n_wakeup_gpio", True, 3) 2596 2597 logger.write_result_to_html_report("Charging 200 mA") 2598 with _bms.charger(16.8, 0.200): 2599 serial_watcher.assert_true("n_wakeup_gpio", False, 4) 2600 2601 def test_overtemp_fault(self, serial_watcher: SerialWatcher): 2602 """ 2603 | Description | Test over temperature Fault | 2604 | :------------------- | :--------------------------------------------------------------------- | 2605 | GitHub Issue | turnaroundfactor/HITL#476 | 2606 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2607jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2608 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2609 | Instructions | 1. Set Default state at 15 </br>\ 2610 2. Resting at 59°C (at or above 59°C) </br>\ 2611 3. Discharging at 100 mA, 59°C (at or above 59°C) </br>\ 2612 4. Charging at 100 mA, 54°C (at or above 53°C) </br>\ 2613 5. Charging at 100 mA, 94°C (at or above 93°C) </br>\ 2614 6. Discharging at 100 mA, 94°C (at or above 93°C) </br>\ 2615 7. Rest at 94°C (at or above 93°C) | 2616 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2617 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2618 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2619 ⦁ Fault overtemp charge value is False (default state) </br>\ 2620 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2621 ⦁ Measure output fets disabled is False (default state) </br>\ 2622 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2623 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2624 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2625 ⦁ After resting permanent disable, permanent disable overtemp is True | 2626 | Estimated Duration | 12 hours | 2627 | Note | If our batteries get too hot, we must trigger a fault. This is \ 2628 common during high discharge or charging cycles. There are 3 different \ 2629 environments where we would trigger an overtemp fault: charge, \ 2630 discharge and resting. While charging, if we are above 53C we must \ 2631 trigger a fault If we are resting or discharging at 59 degrees we must \ 2632 trigger a fault. Both of these faults should trigger a prefault \ 2633 condition in our flags. After we cycle again we should then trigger a \ 2634 full fault. If the temperature ever goes above 93 degrees, the fault \ 2635 should never clear and we should be in permanent fault and trigger \ 2636 the fets. (This should also be seen in the flags) | 2637 """ 2638 logger.write_info_to_report("Testing Overtemp") 2639 assert _bms.load and _bms.charger # Confirm hardware is available 2640 2641 # Test default state 2642 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2643 _plateset.thermistor1 = 15 2644 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 1) 2645 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 1) 2646 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 2647 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 2648 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 2649 serial_watcher.assert_true("flags.measure_output_fets_disabled", False, 1) 2650 2651 # Test resting overtemp 2652 logger.write_result_to_html_report("Resting at 59°C") 2653 _plateset.thermistor1 = 59 2654 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 2) 2655 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 2) 2656 logger.write_result_to_html_report("Resting at 15°C") 2657 _plateset.thermistor1 = 15 2658 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 3) 2659 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 3) 2660 2661 # Test discharging overtemp 2662 logger.write_result_to_html_report("Discharging at -100 mA, 59°C") 2663 with _bms.load(0.100): 2664 _plateset.thermistor1 = 59 2665 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 4) 2666 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 4) 2667 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2668 _plateset.thermistor1 = 15 2669 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 5) 2670 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 5) 2671 2672 # Test charging overtemp 2673 logger.write_result_to_html_report("Charging at 100 mA, 54°C") 2674 with _bms.charger(16.8, 0.200): 2675 _plateset.thermistor1 = 54 2676 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 2677 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 2678 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2679 _plateset.thermistor1 = 15 2680 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 2681 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3) 2682 2683 # Test charging permanent disable 2684 logger.write_result_to_html_report("Charging at 100 mA, 94°C") 2685 _plateset.disengage_safety_protocols = True 2686 _plateset.thermistor1 = 94 2687 _plateset.disengage_safety_protocols = False 2688 with _bms.charger(16.8, 0.200): 2689 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2690 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2691 _plateset.thermistor1 = 15 2692 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2693 2694 # Test discharging permanent disable 2695 logger.write_result_to_html_report("Discharging at -100 mA, 94°C") 2696 _plateset.disengage_safety_protocols = True 2697 _plateset.thermistor1 = 94 2698 _plateset.disengage_safety_protocols = False 2699 with _bms.load(0.100): 2700 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2701 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2702 _plateset.thermistor1 = 15 2703 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2704 2705 # Test resting permanent disable 2706 _plateset.disengage_safety_protocols = True 2707 logger.write_result_to_html_report("Resting at 94°C") 2708 _plateset.thermistor1 = 94 2709 _plateset.disengage_safety_protocols = False 2710 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2711 logger.write_result_to_html_report("Resting at 15°C") 2712 _plateset.thermistor1 = 15 2713 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2714 2715 def test_undertemp_faults(self, serial_watcher: SerialWatcher): 2716 """ 2717 | Description | Test under temperature Fault | 2718 | :------------------- | :--------------------------------------------------------------------- | 2719 | GitHub Issue | turnaroundfactor/HITL#476 | 2720 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2721jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2722 | MIL-PRF Sections | 4.7.2.7 (Low Temperature Discharge) | 2723 | Instructions | 1. Set Default state at 15 </br>\ 2724 2. Resting at -35°C (at or below -33.2°C) </br>\ 2725 3. Discharging at -100 mA, -35°C (at or below -33.2°C) </br>\ 2726 4. Discharging at -100 mA, -30°C (at or above -32.2°C) </br>\ 2727 5. Charging at 100 mA, -25°C (at or below -23.2°C) </br>\ 2728 6. Charging at 100 mA, -20°C (at or above -22.2°C) | 2729 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2730 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2731 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2732 ⦁ Fault overtemp charge value is False (default state) </br>\ 2733 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2734 ⦁ Measure output fets disabled is False (default state) </br>\ 2735 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2736 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2737 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2738 ⦁ After resting permanent disable, permanent disable overtemp is True | 2739 | Estimated Duration | 10 seconds | 2740 | Note | This occurs when we read more than 20 mamps from the battery, if any \ 2741 of the cells are under 0 degrees Celsius this will trigger a fault. \ 2742 This will be cleared if we go above -2C. Regardless of current being \ 2743 measured, if we ever read below -20C, this should trigger a fault. \ 2744 This fault should not be cleared until we are above -18C | 2745 """ 2746 logger.write_info_to_report("Testing Undertemp") 2747 assert _bms.load and _bms.charger # Confirm hardware is available 2748 2749 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2750 _plateset.thermistor1 = 15 2751 2752 # Discharging flags 2753 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 2754 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 2755 2756 # Charging flags 2757 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 2758 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 2759 2760 logger.write_result_to_html_report("Resting at -35°C") 2761 _plateset.thermistor1 = -35 2762 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 2763 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 2764 logger.write_result_to_html_report("Resting at -30°C") 2765 _plateset.thermistor1 = -30 2766 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 2767 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 2768 2769 logger.write_result_to_html_report("Discharging at -100 mA, -35°C") 2770 with _bms.load(0.100): 2771 _plateset.thermistor1 = -35 2772 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 4) 2773 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 4) 2774 logger.write_result_to_html_report("Discharging at -100 mA, -30°C") 2775 _plateset.thermistor1 = -30 2776 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 5) 2777 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 5) 2778 2779 logger.write_result_to_html_report("Charging at 100 mA, -25°C") 2780 with _bms.charger(16.8, 0.200): 2781 _plateset.thermistor1 = -25 2782 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 2783 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 2784 logger.write_result_to_html_report("Charging at 100 mA, 20°C") 2785 _plateset.thermistor1 = 20 2786 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 2787 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3) 2788 2789 def set_exact_volts(self, cell: Cell, voltage: float, compensation: float = 0.08): 2790 """What the BMS reads won't exactly match the set voltage, thus we need slight adjustments.""" 2791 cell.exact_volts = voltage + compensation 2792 logger.write_debug_to_report(f"Cell is {cell.volts}V") 2793 2794 def test_overvoltage_faults(self, serial_watcher: SerialWatcher): 2795 """ 2796 | Description | Test over voltage faults | 2797 | :------------------- | :--------------------------------------------------------------------- | 2798 | GitHub Issue | turnaroundfactor/HITL#476 | 2799 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2800jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2801 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2802 | Instructions | 1. Rest at 0 mA, 3.8002 V </br>\ 2803 2. Charge at 100 mA, 4.21 V </br>\ 2804 3. Charge at 100 mA, 4.10 V </br>\ 2805 4. Charge at 100 mA, 4.26 V </br>\ 2806 5. Charge at 100 mA, 4.10 V | 2807 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 3.8002 V </br>\ 2808 ⦁ Fault over voltage charge is False at 3.8002 V </br>\ 2809 ⦁ Permanent disable over votlage charge is False at 3.8002 V </br>\ 2810 ⦁ Prefault over voltage charge is True at 4.21 V </br>\ 2811 ⦁ Fault over voltage charge is True at 4.21 V </br>\ 2812 ⦁ Prefault over voltage charge is False at 4.10 V </br>\ 2813 ⦁ Fault over voltage charge is False at 4.10 V </br>\ 2814 ⦁ Permanent disable over voltage is True at 4.26 V </br>\ 2815 ⦁ Permanent disable over voltage is False at 4.10 V | 2816 | Estimated Duration | 10 seconds | 2817 | Note | While charging, we need to monitor the voltage of our cells. \ 2818 Specifically, if a cell ever goes above 4.205 Volts, we should \ 2819 trigger a prefault. If this prefault exsists for more than 3 seconds, \ 2820 we then should trigger a full fault. If a cell ever gets to be above \ 2821 4.250 volts, we should trigger a permanent fault. If we go under 4.201 \ 2822 we should be able to clear the fault | 2823 """ 2824 logger.write_info_to_report("Testing Overvoltage") 2825 assert _bms.load and _bms.charger # Confirm hardware is available 2826 test_cell = _bms.cells[1] 2827 for cell in _bms.cells.values(): 2828 cell.disengage_safety_protocols = True 2829 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2830 self.set_exact_volts(cell, 4.0, 0) 2831 2832 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V") 2833 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 2834 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 2835 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 2836 2837 with _bms.charger(16.8, 0.200): 2838 logger.write_result_to_html_report("Charging at 100 mA, 4.205+ V") 2839 self.set_exact_volts(test_cell, 4.22, 0) 2840 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 2841 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 2842 2843 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2844 self.set_exact_volts(test_cell, 4.10, 0) 2845 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 2846 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 2847 2848 logger.write_result_to_html_report("Charging at 100 mA, 4.25+ V") 2849 self.set_exact_volts(test_cell, 4.27, 0) 2850 serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2) 2851 2852 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2853 self.set_exact_volts(test_cell, 4.10, 0) 2854 serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3) 2855 2856 def test_undervoltage_faults(self, serial_watcher: SerialWatcher): 2857 """ 2858 | Description | Test under voltage faults | 2859 | :------------------- | :--------------------------------------------------------------------- | 2860 | GitHub Issue | turnaroundfactor/HITL#476 | 2861 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2862jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2863 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2864 | Instructions | 1. Rest at 0 mA, 3.0 V </br>\ 2865 2. Rest at 0 mA, 2.3 V </br>\ 2866 3. Rest at 0 mA, 2.6 V </br>\ 2867 4. Charge at 500 mA, 2.3 V </br>\ 2868 5. Charge at 500 mA, 2.4 V </br>\ 2869 6. Charge at 500 mA, 2.2 V </br>\ 2870 5. Charge at 500 mA, 2.6 V | 2871 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0mA, 3.0 V </br>\ 2872 ⦁ Fault slumber under voltage discharge is False at 0mA, 3.0 V </br>\ 2873 ⦁ Prefault under voltage charge is False at 0mA, 3.0 V </br>\ 2874 ⦁ Permanent disable under voltage is False at 0mA, 3.0 V </br>\ 2875 ⦁ Prefault under voltage discharge is True when resting at 0mA, 2.3 V </br>\ 2876 ⦁ Fault slumber under voltage is True when resting at 0mA, 2.3 V </br>\ 2877 ⦁ Prefault under voltage discharge is False when resting at 0mA, 2.6 V </br>\ 2878 ⦁ Fault slumber under voltage is False when resting at 0mA, 2.6 V </br>\ 2879 ⦁ Prefault under voltage charge is True when charging at 500mA, 2.3 V </br>\ 2880 ⦁ Fault slumber under voltage is True when charging at 500mA, 2.3 V </br>\ 2881 ⦁ Prefault under voltage discharge is False when charging at 500mA, 2.4 V </br>\ 2882 ⦁ Fault slumber under voltage is False when charging at 500mA, 2.4 V </br>\ 2883 ⦁ Permanent disable under voltage is True when charging at 500mA, 2.2 V </br>\ 2884 ⦁ Permanent disable under voltage is False when charging at 500mA, 2.6 V | 2885 | Estimated Duration | 10 seconds | 2886 | Note | This has also been validated in software, meaning the logic should \ 2887 properly handle a situation with a cell discharging too low, however \ 2888 this has not yet been tested in hardware with a cell sensor reading \ 2889 that low of voltage and triggering a fault. If we are reading less \ 2890 than 20mamps from the cells, we should be able to trigger an \ 2891 under-voltage fault. If we read less than 2.4 volts, we must \ 2892 trigger a fault. If this fault persists for over 1 second, we \ 2893 should then trigger a full fault. We will not clear this fault \ 2894 unless we are able to read above 2.5 volts. If we are reading over \ 2895 20 mamps and a cell reads less than 2.325 volts, we must trigger a \ 2896 cell voltage charge min prefault, if this persists for another bms \ 2897 software cycle we will trigger a full fault. This fault will clear \ 2898 when we read above this voltage. If the cell voltage ever goes under \ 2899 2.3 while charging, we must trigger a permanent fault. | 2900 """ 2901 logger.write_info_to_report("Testing Undervoltage") 2902 assert _bms.load and _bms.charger # Confirm hardware is available 2903 test_cell = _bms.cells[1] 2904 for cell in _bms.cells.values(): 2905 cell.disengage_safety_protocols = True 2906 # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2907 self.set_exact_volts(cell, 2.6, 0.00) 2908 2909 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V") 2910 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 2911 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 2912 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 2913 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 2914 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 2915 2916 with _bms.load(0.500): 2917 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V") 2918 self.set_exact_volts(test_cell, 2.320, 0.00) 2919 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2, wait_time=600) 2920 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2, wait_time=600) 2921 2922 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V") 2923 self.set_exact_volts(test_cell, 2.6, 0.00) 2924 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3, wait_time=600) 2925 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3, wait_time=600) 2926 2927 with _bms.charger(16.8, 0.500): 2928 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V") 2929 self.set_exact_volts(test_cell, 2.320, 0.00) 2930 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2, wait_time=600) 2931 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2, wait_time=600) 2932 2933 logger.write_result_to_html_report("Charging at 500 mA, 2.4 V") 2934 self.set_exact_volts(test_cell, 2.6, 0.00) 2935 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 2936 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 2937 2938 logger.write_result_to_html_report("Charging at 500 mA, 2.2 V") 2939 self.set_exact_volts(test_cell, 2.2, 0.00) 2940 serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2) 2941 2942 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V") 2943 self.set_exact_volts(test_cell, 2.6, 0.00) 2944 serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3) 2945 2946 def test_cell_imbalance(self, serial_watcher: SerialWatcher): 2947 """ 2948 | Description | Test Cell Imbalance | 2949 | :------------------- | :--------------------------------------------------------------------- | 2950 | GitHub Issue | turnaroundfactor/HITL#476 | 2951 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2952jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2953 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 2954 | Instructions | 1. Rest at 0 mA, 3.8 V </br>\ 2955 2. Rest at 0 mA, 2.0 V </br>\ 2956 3. Rest at 0 mA, 3.8 V | 2957 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2958 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2959 ⦁ Prefault cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2960 ⦁ Permanent disable cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2961 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V | 2962 | Estimated Duration | 10 seconds | 2963 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 2964 """ 2965 logger.write_info_to_report("Testing Cell Imbalance") 2966 assert _bms.load and _bms.charger # Confirm hardware is available 2967 test_cell = _bms.cells[1] 2968 test_cell.disengage_safety_protocols = True 2969 2970 # Set all cell voltages 2971 for cell in _bms.cells.values(): 2972 cell.volts = CELL_VOLTAGE 2973 2974 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2975 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2976 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 2977 2978 self.set_exact_volts(test_cell, 2.0) 2979 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2980 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2981 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True, 2) 2982 2983 self.set_exact_volts(test_cell, CELL_VOLTAGE) 2984 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2985 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2986 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", False, 3) 2987 2988 def test_overvoltage_overtemp_faults(self, serial_watcher: SerialWatcher): 2989 """ 2990 | Description | Test combined high temperature & high voltage | 2991 | :------------------- | :--------------------------------------------------------------------- | 2992 | GitHub Issue | turnaroundfactor/HITL#476 | 2993 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2994jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2995 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2996 | Instructions | 1. Rest at 0 mA, 3.8002 V, 15°C </br>\ 2997 2. Charge at 100 mA, 4.21 V, 54°C </br>\ 2998 3. Charge at 100 mA, 4.10 V, 15°C </br>\ 2999 4. Charge at 100 mA, 4.26 V, 94°C </br>\ 3000 5. Charge at 100 mA, 4.10 V, 15°C | 3001 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3002 ⦁ Fault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3003 ⦁ Permanent disable over votlage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3004 ⦁ Prefault over voltage charge is True at 4.21 V, 54°C </br>\ 3005 ⦁ Fault over voltage charge is True at 4.21 V, 54°C </br>\ 3006 ⦁ Prefault over temperature charge is True at 4.21 V, 54°C </br>\ 3007 ⦁ Fault over temperature charge is True at 4.21 V, 54°C </br>\ 3008 ⦁ Prefault over voltage charge is False at 4.10 V, 15°C </br>\ 3009 ⦁ Prefault over temperature charge is False at 4.10 V, 15°C </br>\ 3010 ⦁ Fault over voltage charge is False at 4.10 V, 15°C </br>\ 3011 ⦁ Fault over temperature charge is False at 4.10 V </br>\ 3012 ⦁ Permanent disable over voltage is True at 4.26 V, 94°C </br>\ 3013 ⦁ Permanent disable over temperature is True at 4.26 V, 94°C </br>\ 3014 ⦁ Permanent disable over voltage is False at 4.10 V, 15°C </br>\ 3015 ⦁ Permanent disable over temperature is False at 4.10 V, 15°C | 3016 | Estimated Duration | 10 seconds | 3017 | Note | Combine over voltage and over temperature faults | 3018 """ 3019 logger.write_info_to_report("Testing Overvoltage with Overtemp") 3020 assert _bms.load and _bms.charger # Confirm hardware is available 3021 test_cell = _bms.cells[1] 3022 for cell in _bms.cells.values(): 3023 cell.disengage_safety_protocols = True 3024 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3025 self.set_exact_volts(cell, 4.0, 0.0) 3026 3027 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 3028 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 3029 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 3030 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 3031 3032 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 3033 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 3034 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 3035 3036 with _bms.charger(16.8, 0.200): 3037 logger.write_result_to_html_report("Charging at 100 mA, 4.21 V, 54°C") 3038 self.set_exact_volts(test_cell, 4.22, 0.0) 3039 _plateset.thermistor1 = 54 3040 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 3041 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 3042 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 3043 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 3044 3045 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V, 15°C") 3046 self.set_exact_volts(test_cell, 4.10, 0.0) 3047 _plateset.thermistor1 = 15 3048 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 3049 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 3050 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 3051 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3) 3052 3053 def test_undervoltage_undertemp_faults(self, serial_watcher: SerialWatcher): 3054 """ 3055 | Description | Test combined low temperature & low voltage | 3056 | :------------------- | :--------------------------------------------------------------------- | 3057 | GitHub Issue | turnaroundfactor/HITL#476 | 3058 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3059jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3060 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature Discharge) | 3061 | Instructions | 1. Rest at 0 mA, 3.0 V, 15°C </br>\ 3062 2. Rest at 0 mA, 2.3 V, -21°C </br>\ 3063 3. Rest at 0 mA, 2.6 V, -17°C </br>\ 3064 4. Charge at 500 mA, 2.3 V, -25°C </br>\ 3065 5. Charge at 500 mA, 2.4 V, -20°C </br>\ 3066 6. Charge at 500 mA, 2.2 V, 15°C </br>\ 3067 7. Charge at 500 mA, 2.6 V, 15°C | 3068 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3069 ⦁ Fault slumber under voltage charge is False at 0 mA, 3.0 V, 15°C</br>\ 3070 ⦁ Prefault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3071 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3072 ⦁ Permanent disable under voltage is False at 0 mA, 3.0 V, 15°C </br>\ 3073 ⦁ Prefault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3074 ⦁ Fault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3075 ⦁ Prefault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3076 ⦁ Fault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3077 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3078 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3079 ⦁ Prefault under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3080 ⦁ Fault slumber under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3081 ⦁ Prefault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3082 ⦁ Fault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3083 ⦁ Prefault under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3084 ⦁ Fault slumber under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3085 ⦁ Prefault under temperature discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3086 ⦁ Fault under temperature discharge is False at 0 mA, 2.6 V, -17°C</br>\ 3087 ⦁ Prefault under voltage charge is True at 500 mA, 2.3 V, -25°C </br>\ 3088 ⦁ Fault slumber under voltage charge is True at 500 mA, 2.3 V, -25°C</br>\ 3089 ⦁ Prefault under temperature charge is True at 500 mA, 2.3 V, -25°C</br>\ 3090 ⦁ Fault under temperature charge is True at 500 mA, 2.3 V, -25°C </br>\ 3091 ⦁ Prefault under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3092 ⦁ Fault slumber under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3093 ⦁ Prefault under temperature charge is False at 500 mA, 2.4 V, -20°C</br>\ 3094 ⦁ Fault under temperature charge is False at 500 mA, 2.4 V, -20°C </br>\ 3095 ⦁ Permanent disable under voltage is True at 500 mA, 2.2 V, 15°C </br>\ 3096 ⦁ Permanent disable under voltage is False at 500 mA, 2.6 V, 15°C | 3097 | Estimated Duration | 10 seconds | 3098 | Note | Combine under voltage and under temperature faults | 3099 """ 3100 3101 logger.write_info_to_report("Testing Undervoltage with Undertemp") 3102 assert _bms.load and _bms.charger # Confirm hardware is available 3103 test_cell = _bms.cells[1] 3104 for cell in _bms.cells.values(): 3105 cell.disengage_safety_protocols = True 3106 self.set_exact_volts( 3107 cell, 2.8, 0 3108 ) # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3109 3110 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V, 15°C") 3111 _plateset.thermistor1 = 15 3112 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 3113 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 3114 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 3115 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 3116 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 3117 3118 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 3119 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 3120 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 3121 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 3122 3123 with _bms.load(0.500): 3124 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V, -35°C") 3125 self.set_exact_volts(test_cell, 2.320, 0) 3126 _plateset.thermistor1 = -35 3127 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2) 3128 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2) 3129 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 3130 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 3131 3132 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V, -30°C") 3133 self.set_exact_volts(test_cell, 2.8, 0) 3134 _plateset.thermistor1 = -30 3135 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3) 3136 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3) 3137 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 3138 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 3139 3140 with _bms.charger(16.8, 0.500): 3141 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V, -25°C") 3142 self.set_exact_volts(test_cell, 2.320, 0.00) 3143 _plateset.thermistor1 = -25 3144 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2) 3145 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2) 3146 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 3147 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 3148 3149 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V, -20°C") 3150 self.set_exact_volts(test_cell, 2.8, 0) 3151 _plateset.thermistor1 = -20 3152 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 3153 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 3154 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 3155 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3) 3156 3157 def test_cell_imbalance_charge(self, serial_watcher: SerialWatcher): 3158 """ 3159 | Description | Test Cell Imbalance | 3160 | :------------------- | :--------------------------------------------------------------------- | 3161 | GitHub Issue | turnaroundfactor/HITL#476 | 3162 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3163jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3164 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 3165 | Instructions | 1. Rest at 0 mA </br>\ 3166 2. Charge at 1 mA </br>\ 3167 3. Rest at 0 mA | 3168 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA </br>\ 3169 ⦁ Permanent disable cell imbalance is False after resting at 0mA </br>\ 3170 ⦁ Prefault cell imbalance is True after resting at 1 A </br>\ 3171 ⦁ Permanent disable cell imbalance is True after resting 1 A </br>\ 3172 ⦁ Prefault cell imbalance is True after resting at 0 A </br>\ 3173 ⦁ Permanent disable cell imbalance is False after resting at 0 A | 3174 | Estimated Duration | 10 seconds | 3175 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 3176 """ 3177 logger.write_info_to_report("Testing Cell Imbalance Charge") 3178 assert _bms.load and _bms.charger # Confirm hardware is available 3179 3180 test_cell = _bms.cells[1] 3181 test_cell.disengage_safety_protocols = True 3182 for cell in _bms.cells.values(): 3183 cell.exact_volts = 4.2 3184 3185 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3186 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3187 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 3188 3189 with _bms.charger(16.8, 1): 3190 self.set_exact_volts(test_cell, 2.5) 3191 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3192 logger.write_result_to_html_report(f"Charging at 1 A, {', '.join(voltages)}") 3193 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True) 3194 3195 test_cell.exact_volts = 4.2 3196 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3197 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3198 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True) 3199 3200 def test_current_limit(self, serial_watcher: SerialWatcher): 3201 """ 3202 | Description | Confirm A fault is raised after 3.5A | 3203 | :------------------- | :----------------------------------------------------------------------------------- | 3204 | GitHub Issue | turnaroundfactor/HITL#476 | 3205 | Instructions | 1. Rest for 30 second </br>\ 3206 2. Charge at 3.75 amps (the max limit will be 3.5 amps) </br>\ 3207 3. Verify a prefault and fault are raised. </br>\ 3208 4. Rest until faults clear </br>\ 3209 5. Charge at 3.75 amps </br>\ 3210 6. Verify a prefault occurred but not a fault | 3211 | Pass / Fail Criteria | Pass if faults are raised | 3212 | Estimated Duration | 2 minutes | 3213 | Notes | We use 3.5A as a limit due to hardware limitations | 3214 """ 3215 3216 logger.write_info_to_report("Testing Current Limit") 3217 assert _bms.load and _bms.charger # Confirm hardware is available 3218 3219 # Rest and make sure no faults are active 3220 logger.write_result_to_html_report("Confirm no faults are active") 3221 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 1) 3222 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 1) 3223 3224 # Charge at 3.25 amps and verify faults are raised 3225 logger.write_result_to_html_report("Charging 3.75 A") 3226 with _bms.charger(16.8, 3.75): 3227 serial_watcher.assert_true("flags.prefault_overcurrent_charge", True, 2, wait_time=60) 3228 serial_watcher.assert_true("flags.fault_overcurrent_charge", True, 2, wait_time=60) 3229 3230 # Rest until faults clear 3231 logger.write_result_to_html_report("Resting") 3232 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 3) 3233 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 3) 3234 assert ( 3235 serial_watcher.events["flags.fault_overcurrent_charge"][2].bms_time 3236 - serial_watcher.events["flags.fault_overcurrent_charge"][1].bms_time 3237 ).total_seconds() >= 60 3238 3239 def test_undertemp_charge_rate(self, serial_watcher: SerialWatcher): 3240 """ 3241 | Description | Confirm a fault is raised at or above 3.1A when below 5°C | 3242 | :------------------- | :----------------------------------------------------------------------------------- | 3243 | GitHub Issue | turnaroundfactor/HITL#611 | 3244 | Instructions | 1. Rest for 30 second </br>\ 3245 2. Charge at 3.3 amps below 5°C </br>\ 3246 3. Verify a prefault (before 1 second) and fault (after 1 second) are raised. </br>\ 3247 4. Charge above 7°C until faults clear | 3248 | Pass / Fail Criteria | Pass if faults are raised | 3249 | Estimated Duration | 2 minutes | 3250 """ 3251 3252 logger.write_info_to_report("Testing Undertemp charge rate") 3253 assert _bms.load and _bms.charger # Confirm hardware is available 3254 3255 # Rest and make sure no faults are active 3256 logger.write_result_to_html_report("Confirm no faults are active") 3257 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 1) 3258 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 1) 3259 3260 # Discharge at 3.1+ amps and verify faults are raised 3261 logger.write_result_to_html_report("Discharging at 3.1A+") 3262 with _bms.charger(16.8, 3.3): 3263 _plateset.thermistor1 = 3 3264 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", True, 2, wait_time=60) 3265 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", True, 2, wait_time=60) 3266 _plateset.thermistor1 = 8 3267 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 3, wait_time=90) 3268 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 3, wait_time=90) 3269 3270 def test_overcurrent_discharge_sustained(self, serial_watcher: SerialWatcher): 3271 """ 3272 | Description | Test Over Current Discharge Sustained Fault | 3273 | :------------------- | :--------------------------------------------------------------------- | 3274 | GitHub Issue | turnaroundfactor/HITL#762 | 3275 | Instructions | 1. Check prefault_sw_overcurrent_discharge and fault_sw_overcurrent_discharge </br>\ 3276 2. Set current to less than -2.5 amps </br>\ 3277 3. Confirm faults listed in #1 are true </br>\ 3278 4. Set current to 0 amps (rest) </br>\ 3279 5. Confirm faults listed in #1 are false | 3280 | Pass / Fail Criteria | ⦁ Prefault overCurrent Discharge Sustained is False at start </br>\ 3281 ⦁ Fault overCurrent Discharge Sustained is False at start </br>\ 3282 ⦁ Prefault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3283 ⦁ Fault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3284 ⦁ Prefault overCurrent Discharge Sustained is False when current is 0 A </br>\ 3285 ⦁ Fault overCurrent Discharge Sustained is False when current is 0 A | 3286 | Estimated Duration | 10 seconds | 3287 """ 3288 3289 logger.write_info_to_report("Testing Overcurrent Discharge Sustained Faults") 3290 assert _bms.load and _bms.charger # Confirm hardware is available 3291 3292 logger.write_info_to_report("Sw overcurrent protection high current short time") 3293 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 1) 3294 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 1) 3295 3296 logger.write_result_to_html_report("Setting current to less than -2.6 amps") 3297 with _bms.load(2.6): 3298 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", True, 2) 3299 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", True, 2) 3300 3301 logger.write_result_to_html_report("Resting") 3302 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3303 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3304 3305 logger.write_info_to_report("Sw overcurrent protection low current longer time") 3306 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 1) 3307 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 1) 3308 3309 logger.write_result_to_html_report("Setting current to less than -2.4 amps") 3310 with _bms.load(2.4): 3311 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", True, 2) 3312 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", True, 2) 3313 3314 logger.write_result_to_html_report("Resting") 3315 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10) 3316 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10)
Test all faults
2556 def test_wakeup_1(self, serial_watcher: SerialWatcher): 2557 """ 2558 | Description | Test Wakeup 1 | 2559 | :------------------- | :--------------------------------------------------------------------- | 2560 | GitHub Issue | turnaroundfactor/HITL#476 | 2561 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2562jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2563 | MIL-PRF Sections | 3.6.8.2 (Battery wake up) | 2564 | Instructions | 1. Set Default state at 0mA </br>\ 2565 2. Discharge at 100 mA (below -46 mA) </br>\ 2566 3. Discharge at 10 mA (between -46mA and 19ma) </br>\ 2567 4. Charge at 100 mA, (above 19 mA) | 2568 | Pass / Fail Criteria | ⦁ n_wakeup_gpio = 1, with default state at 0 mA </br>\ 2569 ⦁ n_wakeup_gpio = 0, with default state at 100 mA </br>\ 2570 ⦁ n_wakeup_gpio = 1, with default state at 10 mA </br>\ 2571 ⦁ n_wakeup_gpio = 0, with default state at 0 mA | 2572 | Estimated Duration | 10 seconds | 2573 | Note | The BMS should be in a state of slumber if the current measured is \ 2574 between -46mA and 19ma. This is done using internal comparators on the \ 2575 board to a logical AND chip feeding into an interrupt pin. To test \ 2576 this, 3 different currents should be set. One current below -46ma, \ 2577 another current between -46mA and 19ma, and another current above \ 2578 19ma. If the current is within the allowable range, we should read \ 2579 logic 1 on the N_WAKEUP pin. If the current is outside (above or \ 2580 below) we should read logic 0 on the pin. | 2581 """ 2582 2583 logger.write_info_to_report("Testing Wakeup") 2584 assert _bms.load and _bms.charger # Confirm hardware is available 2585 2586 logger.write_result_to_html_report("Default state") 2587 serial_watcher.assert_true("n_wakeup_gpio", True, 1) 2588 2589 logger.write_result_to_html_report("Discharging 200 mA") 2590 with _bms.load(0.200): 2591 serial_watcher.assert_true("n_wakeup_gpio", False, 2) 2592 2593 logger.write_result_to_html_report("Discharging 10 mA") 2594 with _bms.load(0.010): 2595 serial_watcher.assert_true("n_wakeup_gpio", True, 3) 2596 2597 logger.write_result_to_html_report("Charging 200 mA") 2598 with _bms.charger(16.8, 0.200): 2599 serial_watcher.assert_true("n_wakeup_gpio", False, 4)
| Description | Test Wakeup 1 |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.6.8.2 (Battery wake up) |
| Instructions | 1. Set Default state at 0mA 2. Discharge at 100 mA (below -46 mA) 3. Discharge at 10 mA (between -46mA and 19ma) 4. Charge at 100 mA, (above 19 mA) |
| Pass / Fail Criteria | ⦁ n_wakeup_gpio = 1, with default state at 0 mA ⦁ n_wakeup_gpio = 0, with default state at 100 mA ⦁ n_wakeup_gpio = 1, with default state at 10 mA ⦁ n_wakeup_gpio = 0, with default state at 0 mA |
| Estimated Duration | 10 seconds |
| Note | The BMS should be in a state of slumber if the current measured is between -46mA and 19ma. This is done using internal comparators on the board to a logical AND chip feeding into an interrupt pin. To test this, 3 different currents should be set. One current below -46ma, another current between -46mA and 19ma, and another current above 19ma. If the current is within the allowable range, we should read logic 1 on the N_WAKEUP pin. If the current is outside (above or below) we should read logic 0 on the pin. |
2601 def test_overtemp_fault(self, serial_watcher: SerialWatcher): 2602 """ 2603 | Description | Test over temperature Fault | 2604 | :------------------- | :--------------------------------------------------------------------- | 2605 | GitHub Issue | turnaroundfactor/HITL#476 | 2606 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2607jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2608 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2609 | Instructions | 1. Set Default state at 15 </br>\ 2610 2. Resting at 59°C (at or above 59°C) </br>\ 2611 3. Discharging at 100 mA, 59°C (at or above 59°C) </br>\ 2612 4. Charging at 100 mA, 54°C (at or above 53°C) </br>\ 2613 5. Charging at 100 mA, 94°C (at or above 93°C) </br>\ 2614 6. Discharging at 100 mA, 94°C (at or above 93°C) </br>\ 2615 7. Rest at 94°C (at or above 93°C) | 2616 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2617 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2618 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2619 ⦁ Fault overtemp charge value is False (default state) </br>\ 2620 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2621 ⦁ Measure output fets disabled is False (default state) </br>\ 2622 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2623 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2624 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2625 ⦁ After resting permanent disable, permanent disable overtemp is True | 2626 | Estimated Duration | 12 hours | 2627 | Note | If our batteries get too hot, we must trigger a fault. This is \ 2628 common during high discharge or charging cycles. There are 3 different \ 2629 environments where we would trigger an overtemp fault: charge, \ 2630 discharge and resting. While charging, if we are above 53C we must \ 2631 trigger a fault If we are resting or discharging at 59 degrees we must \ 2632 trigger a fault. Both of these faults should trigger a prefault \ 2633 condition in our flags. After we cycle again we should then trigger a \ 2634 full fault. If the temperature ever goes above 93 degrees, the fault \ 2635 should never clear and we should be in permanent fault and trigger \ 2636 the fets. (This should also be seen in the flags) | 2637 """ 2638 logger.write_info_to_report("Testing Overtemp") 2639 assert _bms.load and _bms.charger # Confirm hardware is available 2640 2641 # Test default state 2642 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2643 _plateset.thermistor1 = 15 2644 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 1) 2645 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 1) 2646 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 2647 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 2648 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 2649 serial_watcher.assert_true("flags.measure_output_fets_disabled", False, 1) 2650 2651 # Test resting overtemp 2652 logger.write_result_to_html_report("Resting at 59°C") 2653 _plateset.thermistor1 = 59 2654 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 2) 2655 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 2) 2656 logger.write_result_to_html_report("Resting at 15°C") 2657 _plateset.thermistor1 = 15 2658 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 3) 2659 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 3) 2660 2661 # Test discharging overtemp 2662 logger.write_result_to_html_report("Discharging at -100 mA, 59°C") 2663 with _bms.load(0.100): 2664 _plateset.thermistor1 = 59 2665 serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 4) 2666 serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 4) 2667 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2668 _plateset.thermistor1 = 15 2669 serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 5) 2670 serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 5) 2671 2672 # Test charging overtemp 2673 logger.write_result_to_html_report("Charging at 100 mA, 54°C") 2674 with _bms.charger(16.8, 0.200): 2675 _plateset.thermistor1 = 54 2676 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 2677 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 2678 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2679 _plateset.thermistor1 = 15 2680 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 2681 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3) 2682 2683 # Test charging permanent disable 2684 logger.write_result_to_html_report("Charging at 100 mA, 94°C") 2685 _plateset.disengage_safety_protocols = True 2686 _plateset.thermistor1 = 94 2687 _plateset.disengage_safety_protocols = False 2688 with _bms.charger(16.8, 0.200): 2689 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2690 logger.write_result_to_html_report("Charging at 100 mA, 15°C") 2691 _plateset.thermistor1 = 15 2692 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2693 2694 # Test discharging permanent disable 2695 logger.write_result_to_html_report("Discharging at -100 mA, 94°C") 2696 _plateset.disengage_safety_protocols = True 2697 _plateset.thermistor1 = 94 2698 _plateset.disengage_safety_protocols = False 2699 with _bms.load(0.100): 2700 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2701 logger.write_result_to_html_report("Discharging at -100 mA, 15°C") 2702 _plateset.thermistor1 = 15 2703 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3) 2704 2705 # Test resting permanent disable 2706 _plateset.disengage_safety_protocols = True 2707 logger.write_result_to_html_report("Resting at 94°C") 2708 _plateset.thermistor1 = 94 2709 _plateset.disengage_safety_protocols = False 2710 serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2) 2711 logger.write_result_to_html_report("Resting at 15°C") 2712 _plateset.thermistor1 = 15 2713 serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3)
| Description | Test over temperature Fault |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) |
| Instructions | 1. Set Default state at 15 2. Resting at 59°C (at or above 59°C) 3. Discharging at 100 mA, 59°C (at or above 59°C) 4. Charging at 100 mA, 54°C (at or above 53°C) 5. Charging at 100 mA, 94°C (at or above 93°C) 6. Discharging at 100 mA, 94°C (at or above 93°C) 7. Rest at 94°C (at or above 93°C) |
| Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) ⦁ Fault overtemp Discharge value is False (default state) ⦁ Prefault overtemp charge value is False (default state) ⦁ Fault overtemp charge value is False (default state) ⦁ Permanent Disable overtemp is False (default state) ⦁ Measure output fets disabled is False (default state) ⦁ Resting overtemp discharge is True for fault & prefault ⦁ Discharging overtemp value is True for fault & prefault ⦁ After charging permanent disable, permanent disable overtemp is True ⦁ After resting permanent disable, permanent disable overtemp is True |
| Estimated Duration | 12 hours |
| Note | If our batteries get too hot, we must trigger a fault. This is common during high discharge or charging cycles. There are 3 different environments where we would trigger an overtemp fault: charge, discharge and resting. While charging, if we are above 53C we must trigger a fault If we are resting or discharging at 59 degrees we must trigger a fault. Both of these faults should trigger a prefault condition in our flags. After we cycle again we should then trigger a full fault. If the temperature ever goes above 93 degrees, the fault should never clear and we should be in permanent fault and trigger the fets. (This should also be seen in the flags) |
2715 def test_undertemp_faults(self, serial_watcher: SerialWatcher): 2716 """ 2717 | Description | Test under temperature Fault | 2718 | :------------------- | :--------------------------------------------------------------------- | 2719 | GitHub Issue | turnaroundfactor/HITL#476 | 2720 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2721jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2722 | MIL-PRF Sections | 4.7.2.7 (Low Temperature Discharge) | 2723 | Instructions | 1. Set Default state at 15 </br>\ 2724 2. Resting at -35°C (at or below -33.2°C) </br>\ 2725 3. Discharging at -100 mA, -35°C (at or below -33.2°C) </br>\ 2726 4. Discharging at -100 mA, -30°C (at or above -32.2°C) </br>\ 2727 5. Charging at 100 mA, -25°C (at or below -23.2°C) </br>\ 2728 6. Charging at 100 mA, -20°C (at or above -22.2°C) | 2729 | Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) </br>\ 2730 ⦁ Fault overtemp Discharge value is False (default state) </br>\ 2731 ⦁ Prefault overtemp charge value is False (default state) </br>\ 2732 ⦁ Fault overtemp charge value is False (default state) </br>\ 2733 ⦁ Permanent Disable overtemp is False (default state) </br>\ 2734 ⦁ Measure output fets disabled is False (default state) </br>\ 2735 ⦁ Resting overtemp discharge is True for fault & prefault </br>\ 2736 ⦁ Discharging overtemp value is True for fault & prefault </br>\ 2737 ⦁ After charging permanent disable, permanent disable overtemp is True </br>\ 2738 ⦁ After resting permanent disable, permanent disable overtemp is True | 2739 | Estimated Duration | 10 seconds | 2740 | Note | This occurs when we read more than 20 mamps from the battery, if any \ 2741 of the cells are under 0 degrees Celsius this will trigger a fault. \ 2742 This will be cleared if we go above -2C. Regardless of current being \ 2743 measured, if we ever read below -20C, this should trigger a fault. \ 2744 This fault should not be cleared until we are above -18C | 2745 """ 2746 logger.write_info_to_report("Testing Undertemp") 2747 assert _bms.load and _bms.charger # Confirm hardware is available 2748 2749 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 2750 _plateset.thermistor1 = 15 2751 2752 # Discharging flags 2753 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 2754 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 2755 2756 # Charging flags 2757 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 2758 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 2759 2760 logger.write_result_to_html_report("Resting at -35°C") 2761 _plateset.thermistor1 = -35 2762 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 2763 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 2764 logger.write_result_to_html_report("Resting at -30°C") 2765 _plateset.thermistor1 = -30 2766 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 2767 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 2768 2769 logger.write_result_to_html_report("Discharging at -100 mA, -35°C") 2770 with _bms.load(0.100): 2771 _plateset.thermistor1 = -35 2772 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 4) 2773 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 4) 2774 logger.write_result_to_html_report("Discharging at -100 mA, -30°C") 2775 _plateset.thermistor1 = -30 2776 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 5) 2777 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 5) 2778 2779 logger.write_result_to_html_report("Charging at 100 mA, -25°C") 2780 with _bms.charger(16.8, 0.200): 2781 _plateset.thermistor1 = -25 2782 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 2783 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 2784 logger.write_result_to_html_report("Charging at 100 mA, 20°C") 2785 _plateset.thermistor1 = 20 2786 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 2787 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
| Description | Test under temperature Fault |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.2.7 (Low Temperature Discharge) |
| Instructions | 1. Set Default state at 15 2. Resting at -35°C (at or below -33.2°C) 3. Discharging at -100 mA, -35°C (at or below -33.2°C) 4. Discharging at -100 mA, -30°C (at or above -32.2°C) 5. Charging at 100 mA, -25°C (at or below -23.2°C) 6. Charging at 100 mA, -20°C (at or above -22.2°C) |
| Pass / Fail Criteria | ⦁ Prefault overtemp Discharge value is False (default state) ⦁ Fault overtemp Discharge value is False (default state) ⦁ Prefault overtemp charge value is False (default state) ⦁ Fault overtemp charge value is False (default state) ⦁ Permanent Disable overtemp is False (default state) ⦁ Measure output fets disabled is False (default state) ⦁ Resting overtemp discharge is True for fault & prefault ⦁ Discharging overtemp value is True for fault & prefault ⦁ After charging permanent disable, permanent disable overtemp is True ⦁ After resting permanent disable, permanent disable overtemp is True |
| Estimated Duration | 10 seconds |
| Note | This occurs when we read more than 20 mamps from the battery, if any of the cells are under 0 degrees Celsius this will trigger a fault. This will be cleared if we go above -2C. Regardless of current being measured, if we ever read below -20C, this should trigger a fault. This fault should not be cleared until we are above -18C |
2789 def set_exact_volts(self, cell: Cell, voltage: float, compensation: float = 0.08): 2790 """What the BMS reads won't exactly match the set voltage, thus we need slight adjustments.""" 2791 cell.exact_volts = voltage + compensation 2792 logger.write_debug_to_report(f"Cell is {cell.volts}V")
What the BMS reads won't exactly match the set voltage, thus we need slight adjustments.
2794 def test_overvoltage_faults(self, serial_watcher: SerialWatcher): 2795 """ 2796 | Description | Test over voltage faults | 2797 | :------------------- | :--------------------------------------------------------------------- | 2798 | GitHub Issue | turnaroundfactor/HITL#476 | 2799 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2800jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2801 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2802 | Instructions | 1. Rest at 0 mA, 3.8002 V </br>\ 2803 2. Charge at 100 mA, 4.21 V </br>\ 2804 3. Charge at 100 mA, 4.10 V </br>\ 2805 4. Charge at 100 mA, 4.26 V </br>\ 2806 5. Charge at 100 mA, 4.10 V | 2807 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 3.8002 V </br>\ 2808 ⦁ Fault over voltage charge is False at 3.8002 V </br>\ 2809 ⦁ Permanent disable over votlage charge is False at 3.8002 V </br>\ 2810 ⦁ Prefault over voltage charge is True at 4.21 V </br>\ 2811 ⦁ Fault over voltage charge is True at 4.21 V </br>\ 2812 ⦁ Prefault over voltage charge is False at 4.10 V </br>\ 2813 ⦁ Fault over voltage charge is False at 4.10 V </br>\ 2814 ⦁ Permanent disable over voltage is True at 4.26 V </br>\ 2815 ⦁ Permanent disable over voltage is False at 4.10 V | 2816 | Estimated Duration | 10 seconds | 2817 | Note | While charging, we need to monitor the voltage of our cells. \ 2818 Specifically, if a cell ever goes above 4.205 Volts, we should \ 2819 trigger a prefault. If this prefault exsists for more than 3 seconds, \ 2820 we then should trigger a full fault. If a cell ever gets to be above \ 2821 4.250 volts, we should trigger a permanent fault. If we go under 4.201 \ 2822 we should be able to clear the fault | 2823 """ 2824 logger.write_info_to_report("Testing Overvoltage") 2825 assert _bms.load and _bms.charger # Confirm hardware is available 2826 test_cell = _bms.cells[1] 2827 for cell in _bms.cells.values(): 2828 cell.disengage_safety_protocols = True 2829 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2830 self.set_exact_volts(cell, 4.0, 0) 2831 2832 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V") 2833 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 2834 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 2835 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 2836 2837 with _bms.charger(16.8, 0.200): 2838 logger.write_result_to_html_report("Charging at 100 mA, 4.205+ V") 2839 self.set_exact_volts(test_cell, 4.22, 0) 2840 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 2841 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 2842 2843 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2844 self.set_exact_volts(test_cell, 4.10, 0) 2845 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 2846 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 2847 2848 logger.write_result_to_html_report("Charging at 100 mA, 4.25+ V") 2849 self.set_exact_volts(test_cell, 4.27, 0) 2850 serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2) 2851 2852 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V") 2853 self.set_exact_volts(test_cell, 4.10, 0) 2854 serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3)
| Description | Test over voltage faults |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.6.1 (Standard Charge) |
| Instructions | 1. Rest at 0 mA, 3.8002 V 2. Charge at 100 mA, 4.21 V 3. Charge at 100 mA, 4.10 V 4. Charge at 100 mA, 4.26 V 5. Charge at 100 mA, 4.10 V |
| Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 3.8002 V ⦁ Fault over voltage charge is False at 3.8002 V ⦁ Permanent disable over votlage charge is False at 3.8002 V ⦁ Prefault over voltage charge is True at 4.21 V ⦁ Fault over voltage charge is True at 4.21 V ⦁ Prefault over voltage charge is False at 4.10 V ⦁ Fault over voltage charge is False at 4.10 V ⦁ Permanent disable over voltage is True at 4.26 V ⦁ Permanent disable over voltage is False at 4.10 V |
| Estimated Duration | 10 seconds |
| Note | While charging, we need to monitor the voltage of our cells. Specifically, if a cell ever goes above 4.205 Volts, we should trigger a prefault. If this prefault exsists for more than 3 seconds, we then should trigger a full fault. If a cell ever gets to be above 4.250 volts, we should trigger a permanent fault. If we go under 4.201 we should be able to clear the fault |
2856 def test_undervoltage_faults(self, serial_watcher: SerialWatcher): 2857 """ 2858 | Description | Test under voltage faults | 2859 | :------------------- | :--------------------------------------------------------------------- | 2860 | GitHub Issue | turnaroundfactor/HITL#476 | 2861 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2862jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2863 | MIL-PRF Sections | 4.6.1 (Standard Charge) | 2864 | Instructions | 1. Rest at 0 mA, 3.0 V </br>\ 2865 2. Rest at 0 mA, 2.3 V </br>\ 2866 3. Rest at 0 mA, 2.6 V </br>\ 2867 4. Charge at 500 mA, 2.3 V </br>\ 2868 5. Charge at 500 mA, 2.4 V </br>\ 2869 6. Charge at 500 mA, 2.2 V </br>\ 2870 5. Charge at 500 mA, 2.6 V | 2871 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0mA, 3.0 V </br>\ 2872 ⦁ Fault slumber under voltage discharge is False at 0mA, 3.0 V </br>\ 2873 ⦁ Prefault under voltage charge is False at 0mA, 3.0 V </br>\ 2874 ⦁ Permanent disable under voltage is False at 0mA, 3.0 V </br>\ 2875 ⦁ Prefault under voltage discharge is True when resting at 0mA, 2.3 V </br>\ 2876 ⦁ Fault slumber under voltage is True when resting at 0mA, 2.3 V </br>\ 2877 ⦁ Prefault under voltage discharge is False when resting at 0mA, 2.6 V </br>\ 2878 ⦁ Fault slumber under voltage is False when resting at 0mA, 2.6 V </br>\ 2879 ⦁ Prefault under voltage charge is True when charging at 500mA, 2.3 V </br>\ 2880 ⦁ Fault slumber under voltage is True when charging at 500mA, 2.3 V </br>\ 2881 ⦁ Prefault under voltage discharge is False when charging at 500mA, 2.4 V </br>\ 2882 ⦁ Fault slumber under voltage is False when charging at 500mA, 2.4 V </br>\ 2883 ⦁ Permanent disable under voltage is True when charging at 500mA, 2.2 V </br>\ 2884 ⦁ Permanent disable under voltage is False when charging at 500mA, 2.6 V | 2885 | Estimated Duration | 10 seconds | 2886 | Note | This has also been validated in software, meaning the logic should \ 2887 properly handle a situation with a cell discharging too low, however \ 2888 this has not yet been tested in hardware with a cell sensor reading \ 2889 that low of voltage and triggering a fault. If we are reading less \ 2890 than 20mamps from the cells, we should be able to trigger an \ 2891 under-voltage fault. If we read less than 2.4 volts, we must \ 2892 trigger a fault. If this fault persists for over 1 second, we \ 2893 should then trigger a full fault. We will not clear this fault \ 2894 unless we are able to read above 2.5 volts. If we are reading over \ 2895 20 mamps and a cell reads less than 2.325 volts, we must trigger a \ 2896 cell voltage charge min prefault, if this persists for another bms \ 2897 software cycle we will trigger a full fault. This fault will clear \ 2898 when we read above this voltage. If the cell voltage ever goes under \ 2899 2.3 while charging, we must trigger a permanent fault. | 2900 """ 2901 logger.write_info_to_report("Testing Undervoltage") 2902 assert _bms.load and _bms.charger # Confirm hardware is available 2903 test_cell = _bms.cells[1] 2904 for cell in _bms.cells.values(): 2905 cell.disengage_safety_protocols = True 2906 # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 2907 self.set_exact_volts(cell, 2.6, 0.00) 2908 2909 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V") 2910 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 2911 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 2912 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 2913 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 2914 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 2915 2916 with _bms.load(0.500): 2917 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V") 2918 self.set_exact_volts(test_cell, 2.320, 0.00) 2919 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2, wait_time=600) 2920 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2, wait_time=600) 2921 2922 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V") 2923 self.set_exact_volts(test_cell, 2.6, 0.00) 2924 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3, wait_time=600) 2925 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3, wait_time=600) 2926 2927 with _bms.charger(16.8, 0.500): 2928 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V") 2929 self.set_exact_volts(test_cell, 2.320, 0.00) 2930 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2, wait_time=600) 2931 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2, wait_time=600) 2932 2933 logger.write_result_to_html_report("Charging at 500 mA, 2.4 V") 2934 self.set_exact_volts(test_cell, 2.6, 0.00) 2935 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 2936 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 2937 2938 logger.write_result_to_html_report("Charging at 500 mA, 2.2 V") 2939 self.set_exact_volts(test_cell, 2.2, 0.00) 2940 serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2) 2941 2942 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V") 2943 self.set_exact_volts(test_cell, 2.6, 0.00) 2944 serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3)
| Description | Test under voltage faults |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.6.1 (Standard Charge) |
| Instructions | 1. Rest at 0 mA, 3.0 V 2. Rest at 0 mA, 2.3 V 3. Rest at 0 mA, 2.6 V 4. Charge at 500 mA, 2.3 V 5. Charge at 500 mA, 2.4 V 6. Charge at 500 mA, 2.2 V 5. Charge at 500 mA, 2.6 V |
| Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0mA, 3.0 V ⦁ Fault slumber under voltage discharge is False at 0mA, 3.0 V ⦁ Prefault under voltage charge is False at 0mA, 3.0 V ⦁ Permanent disable under voltage is False at 0mA, 3.0 V ⦁ Prefault under voltage discharge is True when resting at 0mA, 2.3 V ⦁ Fault slumber under voltage is True when resting at 0mA, 2.3 V ⦁ Prefault under voltage discharge is False when resting at 0mA, 2.6 V ⦁ Fault slumber under voltage is False when resting at 0mA, 2.6 V ⦁ Prefault under voltage charge is True when charging at 500mA, 2.3 V ⦁ Fault slumber under voltage is True when charging at 500mA, 2.3 V ⦁ Prefault under voltage discharge is False when charging at 500mA, 2.4 V ⦁ Fault slumber under voltage is False when charging at 500mA, 2.4 V ⦁ Permanent disable under voltage is True when charging at 500mA, 2.2 V ⦁ Permanent disable under voltage is False when charging at 500mA, 2.6 V |
| Estimated Duration | 10 seconds |
| Note | This has also been validated in software, meaning the logic should properly handle a situation with a cell discharging too low, however this has not yet been tested in hardware with a cell sensor reading that low of voltage and triggering a fault. If we are reading less than 20mamps from the cells, we should be able to trigger an under-voltage fault. If we read less than 2.4 volts, we must trigger a fault. If this fault persists for over 1 second, we should then trigger a full fault. We will not clear this fault unless we are able to read above 2.5 volts. If we are reading over 20 mamps and a cell reads less than 2.325 volts, we must trigger a cell voltage charge min prefault, if this persists for another bms software cycle we will trigger a full fault. This fault will clear when we read above this voltage. If the cell voltage ever goes under 2.3 while charging, we must trigger a permanent fault. |
2946 def test_cell_imbalance(self, serial_watcher: SerialWatcher): 2947 """ 2948 | Description | Test Cell Imbalance | 2949 | :------------------- | :--------------------------------------------------------------------- | 2950 | GitHub Issue | turnaroundfactor/HITL#476 | 2951 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2952jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2953 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 2954 | Instructions | 1. Rest at 0 mA, 3.8 V </br>\ 2955 2. Rest at 0 mA, 2.0 V </br>\ 2956 3. Rest at 0 mA, 3.8 V | 2957 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2958 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V </br>\ 2959 ⦁ Prefault cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2960 ⦁ Permanent disable cell imbalance is True after resting at 0mA, 2.0 V </br>\ 2961 ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V | 2962 | Estimated Duration | 10 seconds | 2963 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 2964 """ 2965 logger.write_info_to_report("Testing Cell Imbalance") 2966 assert _bms.load and _bms.charger # Confirm hardware is available 2967 test_cell = _bms.cells[1] 2968 test_cell.disengage_safety_protocols = True 2969 2970 # Set all cell voltages 2971 for cell in _bms.cells.values(): 2972 cell.volts = CELL_VOLTAGE 2973 2974 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2975 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2976 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 2977 2978 self.set_exact_volts(test_cell, 2.0) 2979 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2980 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2981 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True, 2) 2982 2983 self.set_exact_volts(test_cell, CELL_VOLTAGE) 2984 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 2985 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 2986 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", False, 3)
| Description | Test Cell Imbalance |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.2.1 (Cell balance) |
| Instructions | 1. Rest at 0 mA, 3.8 V 2. Rest at 0 mA, 2.0 V 3. Rest at 0 mA, 3.8 V |
| Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA, 3.8 V ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V ⦁ Prefault cell imbalance is True after resting at 0mA, 2.0 V ⦁ Permanent disable cell imbalance is True after resting at 0mA, 2.0 V ⦁ Permanent disable cell imbalance is False after resting at 0mA, 3.8 V |
| Estimated Duration | 10 seconds |
| Note | Occurs when the difference between the highest and lowest cell is 0.5V. |
2988 def test_overvoltage_overtemp_faults(self, serial_watcher: SerialWatcher): 2989 """ 2990 | Description | Test combined high temperature & high voltage | 2991 | :------------------- | :--------------------------------------------------------------------- | 2992 | GitHub Issue | turnaroundfactor/HITL#476 | 2993 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 2994jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 2995 | MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) | 2996 | Instructions | 1. Rest at 0 mA, 3.8002 V, 15°C </br>\ 2997 2. Charge at 100 mA, 4.21 V, 54°C </br>\ 2998 3. Charge at 100 mA, 4.10 V, 15°C </br>\ 2999 4. Charge at 100 mA, 4.26 V, 94°C </br>\ 3000 5. Charge at 100 mA, 4.10 V, 15°C | 3001 | Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3002 ⦁ Fault over voltage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3003 ⦁ Permanent disable over votlage charge is False at 0 mA, 3.8002 V, 15°C </br>\ 3004 ⦁ Prefault over voltage charge is True at 4.21 V, 54°C </br>\ 3005 ⦁ Fault over voltage charge is True at 4.21 V, 54°C </br>\ 3006 ⦁ Prefault over temperature charge is True at 4.21 V, 54°C </br>\ 3007 ⦁ Fault over temperature charge is True at 4.21 V, 54°C </br>\ 3008 ⦁ Prefault over voltage charge is False at 4.10 V, 15°C </br>\ 3009 ⦁ Prefault over temperature charge is False at 4.10 V, 15°C </br>\ 3010 ⦁ Fault over voltage charge is False at 4.10 V, 15°C </br>\ 3011 ⦁ Fault over temperature charge is False at 4.10 V </br>\ 3012 ⦁ Permanent disable over voltage is True at 4.26 V, 94°C </br>\ 3013 ⦁ Permanent disable over temperature is True at 4.26 V, 94°C </br>\ 3014 ⦁ Permanent disable over voltage is False at 4.10 V, 15°C </br>\ 3015 ⦁ Permanent disable over temperature is False at 4.10 V, 15°C | 3016 | Estimated Duration | 10 seconds | 3017 | Note | Combine over voltage and over temperature faults | 3018 """ 3019 logger.write_info_to_report("Testing Overvoltage with Overtemp") 3020 assert _bms.load and _bms.charger # Confirm hardware is available 3021 test_cell = _bms.cells[1] 3022 for cell in _bms.cells.values(): 3023 cell.disengage_safety_protocols = True 3024 # Must be high enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3025 self.set_exact_volts(cell, 4.0, 0.0) 3026 3027 logger.write_result_to_html_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C") 3028 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1) 3029 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1) 3030 serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1) 3031 3032 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1) 3033 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1) 3034 serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1) 3035 3036 with _bms.charger(16.8, 0.200): 3037 logger.write_result_to_html_report("Charging at 100 mA, 4.21 V, 54°C") 3038 self.set_exact_volts(test_cell, 4.22, 0.0) 3039 _plateset.thermistor1 = 54 3040 serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2) 3041 serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2) 3042 serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2) 3043 serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2) 3044 3045 logger.write_result_to_html_report("Charging at 100 mA, 4.10 V, 15°C") 3046 self.set_exact_volts(test_cell, 4.10, 0.0) 3047 _plateset.thermistor1 = 15 3048 serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3) 3049 serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3) 3050 serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3) 3051 serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3)
| Description | Test combined high temperature & high voltage |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.3.3 (Extreme high temperature discharge) |
| Instructions | 1. Rest at 0 mA, 3.8002 V, 15°C 2. Charge at 100 mA, 4.21 V, 54°C 3. Charge at 100 mA, 4.10 V, 15°C 4. Charge at 100 mA, 4.26 V, 94°C 5. Charge at 100 mA, 4.10 V, 15°C |
| Pass / Fail Criteria | ⦁ Prefault over voltage charge is False at 0 mA, 3.8002 V, 15°C ⦁ Fault over voltage charge is False at 0 mA, 3.8002 V, 15°C ⦁ Permanent disable over votlage charge is False at 0 mA, 3.8002 V, 15°C ⦁ Prefault over voltage charge is True at 4.21 V, 54°C ⦁ Fault over voltage charge is True at 4.21 V, 54°C ⦁ Prefault over temperature charge is True at 4.21 V, 54°C ⦁ Fault over temperature charge is True at 4.21 V, 54°C ⦁ Prefault over voltage charge is False at 4.10 V, 15°C ⦁ Prefault over temperature charge is False at 4.10 V, 15°C ⦁ Fault over voltage charge is False at 4.10 V, 15°C ⦁ Fault over temperature charge is False at 4.10 V ⦁ Permanent disable over voltage is True at 4.26 V, 94°C ⦁ Permanent disable over temperature is True at 4.26 V, 94°C ⦁ Permanent disable over voltage is False at 4.10 V, 15°C ⦁ Permanent disable over temperature is False at 4.10 V, 15°C |
| Estimated Duration | 10 seconds |
| Note | Combine over voltage and over temperature faults |
3053 def test_undervoltage_undertemp_faults(self, serial_watcher: SerialWatcher): 3054 """ 3055 | Description | Test combined low temperature & low voltage | 3056 | :------------------- | :--------------------------------------------------------------------- | 3057 | GitHub Issue | turnaroundfactor/HITL#476 | 3058 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3059jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3060 | MIL-PRF Sections | 4.7.3.2 (Extreme low temperature Discharge) | 3061 | Instructions | 1. Rest at 0 mA, 3.0 V, 15°C </br>\ 3062 2. Rest at 0 mA, 2.3 V, -21°C </br>\ 3063 3. Rest at 0 mA, 2.6 V, -17°C </br>\ 3064 4. Charge at 500 mA, 2.3 V, -25°C </br>\ 3065 5. Charge at 500 mA, 2.4 V, -20°C </br>\ 3066 6. Charge at 500 mA, 2.2 V, 15°C </br>\ 3067 7. Charge at 500 mA, 2.6 V, 15°C | 3068 | Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3069 ⦁ Fault slumber under voltage charge is False at 0 mA, 3.0 V, 15°C</br>\ 3070 ⦁ Prefault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3071 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3072 ⦁ Permanent disable under voltage is False at 0 mA, 3.0 V, 15°C </br>\ 3073 ⦁ Prefault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3074 ⦁ Fault under temperature discharge is False at 0 mA, 3.0 V, 15°C </br>\ 3075 ⦁ Prefault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3076 ⦁ Fault under temperature charge is False at 0 mA, 3.0 V, 15°C </br>\ 3077 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3078 ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C </br>\ 3079 ⦁ Prefault under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3080 ⦁ Fault slumber under voltage discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3081 ⦁ Prefault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3082 ⦁ Fault under temperature discharge is True at 0 mA, 2.3 V, -21°C </br>\ 3083 ⦁ Prefault under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3084 ⦁ Fault slumber under voltage discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3085 ⦁ Prefault under temperature discharge is False at 0 mA, 2.6 V, -17°C </br>\ 3086 ⦁ Fault under temperature discharge is False at 0 mA, 2.6 V, -17°C</br>\ 3087 ⦁ Prefault under voltage charge is True at 500 mA, 2.3 V, -25°C </br>\ 3088 ⦁ Fault slumber under voltage charge is True at 500 mA, 2.3 V, -25°C</br>\ 3089 ⦁ Prefault under temperature charge is True at 500 mA, 2.3 V, -25°C</br>\ 3090 ⦁ Fault under temperature charge is True at 500 mA, 2.3 V, -25°C </br>\ 3091 ⦁ Prefault under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3092 ⦁ Fault slumber under voltage charge is False at 500 mA, 2.4 V, -20°C </br>\ 3093 ⦁ Prefault under temperature charge is False at 500 mA, 2.4 V, -20°C</br>\ 3094 ⦁ Fault under temperature charge is False at 500 mA, 2.4 V, -20°C </br>\ 3095 ⦁ Permanent disable under voltage is True at 500 mA, 2.2 V, 15°C </br>\ 3096 ⦁ Permanent disable under voltage is False at 500 mA, 2.6 V, 15°C | 3097 | Estimated Duration | 10 seconds | 3098 | Note | Combine under voltage and under temperature faults | 3099 """ 3100 3101 logger.write_info_to_report("Testing Undervoltage with Undertemp") 3102 assert _bms.load and _bms.charger # Confirm hardware is available 3103 test_cell = _bms.cells[1] 3104 for cell in _bms.cells.values(): 3105 cell.disengage_safety_protocols = True 3106 self.set_exact_volts( 3107 cell, 2.8, 0 3108 ) # Must be low enough to not trigger cell imbalance [abs(high - low) > 0.5V] 3109 3110 logger.write_result_to_html_report("Resting at 0 mA, 3.0 V, 15°C") 3111 _plateset.thermistor1 = 15 3112 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1) 3113 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1) 3114 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1) 3115 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1) 3116 serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1) 3117 3118 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1) 3119 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1) 3120 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1) 3121 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1) 3122 3123 with _bms.load(0.500): 3124 logger.write_result_to_html_report("Discharging at -500 mA, 2.325 V, -35°C") 3125 self.set_exact_volts(test_cell, 2.320, 0) 3126 _plateset.thermistor1 = -35 3127 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2) 3128 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2) 3129 serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2) 3130 serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2) 3131 3132 logger.write_result_to_html_report("Discharging at -500 mA, 2.6 V, -30°C") 3133 self.set_exact_volts(test_cell, 2.8, 0) 3134 _plateset.thermistor1 = -30 3135 serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3) 3136 serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3) 3137 serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3) 3138 serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3) 3139 3140 with _bms.charger(16.8, 0.500): 3141 logger.write_result_to_html_report("Charging at 500 mA, 2.325 V, -25°C") 3142 self.set_exact_volts(test_cell, 2.320, 0.00) 3143 _plateset.thermistor1 = -25 3144 serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2) 3145 serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2) 3146 serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2) 3147 serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2) 3148 3149 logger.write_result_to_html_report("Charging at 500 mA, 2.6 V, -20°C") 3150 self.set_exact_volts(test_cell, 2.8, 0) 3151 _plateset.thermistor1 = -20 3152 serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3) 3153 serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3) 3154 serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3) 3155 serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
| Description | Test combined low temperature & low voltage |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.3.2 (Extreme low temperature Discharge) |
| Instructions | 1. Rest at 0 mA, 3.0 V, 15°C 2. Rest at 0 mA, 2.3 V, -21°C 3. Rest at 0 mA, 2.6 V, -17°C 4. Charge at 500 mA, 2.3 V, -25°C 5. Charge at 500 mA, 2.4 V, -20°C 6. Charge at 500 mA, 2.2 V, 15°C 7. Charge at 500 mA, 2.6 V, 15°C |
| Pass / Fail Criteria | ⦁ Prefault under voltage discharge is False at 0 mA, 3.0 V, 15°C ⦁ Fault slumber under voltage charge is False at 0 mA, 3.0 V, 15°C ⦁ Prefault under voltage charge is False at 0 mA, 3.0 V, 15°C ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C ⦁ Permanent disable under voltage is False at 0 mA, 3.0 V, 15°C ⦁ Prefault under temperature discharge is False at 0 mA, 3.0 V, 15°C ⦁ Fault under temperature discharge is False at 0 mA, 3.0 V, 15°C ⦁ Prefault under temperature charge is False at 0 mA, 3.0 V, 15°C ⦁ Fault under temperature charge is False at 0 mA, 3.0 V, 15°C ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C ⦁ Fault under voltage charge is False at 0 mA, 3.0 V, 15°C ⦁ Prefault under voltage discharge is True at 0 mA, 2.3 V, -21°C ⦁ Fault slumber under voltage discharge is True at 0 mA, 2.3 V, -21°C ⦁ Prefault under temperature discharge is True at 0 mA, 2.3 V, -21°C ⦁ Fault under temperature discharge is True at 0 mA, 2.3 V, -21°C ⦁ Prefault under voltage discharge is False at 0 mA, 2.6 V, -17°C ⦁ Fault slumber under voltage discharge is False at 0 mA, 2.6 V, -17°C ⦁ Prefault under temperature discharge is False at 0 mA, 2.6 V, -17°C ⦁ Fault under temperature discharge is False at 0 mA, 2.6 V, -17°C ⦁ Prefault under voltage charge is True at 500 mA, 2.3 V, -25°C ⦁ Fault slumber under voltage charge is True at 500 mA, 2.3 V, -25°C ⦁ Prefault under temperature charge is True at 500 mA, 2.3 V, -25°C ⦁ Fault under temperature charge is True at 500 mA, 2.3 V, -25°C ⦁ Prefault under voltage charge is False at 500 mA, 2.4 V, -20°C ⦁ Fault slumber under voltage charge is False at 500 mA, 2.4 V, -20°C ⦁ Prefault under temperature charge is False at 500 mA, 2.4 V, -20°C ⦁ Fault under temperature charge is False at 500 mA, 2.4 V, -20°C ⦁ Permanent disable under voltage is True at 500 mA, 2.2 V, 15°C ⦁ Permanent disable under voltage is False at 500 mA, 2.6 V, 15°C |
| Estimated Duration | 10 seconds |
| Note | Combine under voltage and under temperature faults |
3157 def test_cell_imbalance_charge(self, serial_watcher: SerialWatcher): 3158 """ 3159 | Description | Test Cell Imbalance | 3160 | :------------------- | :--------------------------------------------------------------------- | 3161 | GitHub Issue | turnaroundfactor/HITL#476 | 3162 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3163jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D6) | 3164 | MIL-PRF Sections | 4.7.2.1 (Cell balance) | 3165 | Instructions | 1. Rest at 0 mA </br>\ 3166 2. Charge at 1 mA </br>\ 3167 3. Rest at 0 mA | 3168 | Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA </br>\ 3169 ⦁ Permanent disable cell imbalance is False after resting at 0mA </br>\ 3170 ⦁ Prefault cell imbalance is True after resting at 1 A </br>\ 3171 ⦁ Permanent disable cell imbalance is True after resting 1 A </br>\ 3172 ⦁ Prefault cell imbalance is True after resting at 0 A </br>\ 3173 ⦁ Permanent disable cell imbalance is False after resting at 0 A | 3174 | Estimated Duration | 10 seconds | 3175 | Note | Occurs when the difference between the highest and lowest cell is 0.5V.| 3176 """ 3177 logger.write_info_to_report("Testing Cell Imbalance Charge") 3178 assert _bms.load and _bms.charger # Confirm hardware is available 3179 3180 test_cell = _bms.cells[1] 3181 test_cell.disengage_safety_protocols = True 3182 for cell in _bms.cells.values(): 3183 cell.exact_volts = 4.2 3184 3185 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3186 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3187 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1) 3188 3189 with _bms.charger(16.8, 1): 3190 self.set_exact_volts(test_cell, 2.5) 3191 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3192 logger.write_result_to_html_report(f"Charging at 1 A, {', '.join(voltages)}") 3193 serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True) 3194 3195 test_cell.exact_volts = 4.2 3196 voltages = [f"{cell.measured_volts} V" for cell in _bms.cells.values()] 3197 logger.write_result_to_html_report(f"Resting at 0 mA, {', '.join(voltages)}") 3198 serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True)
| Description | Test Cell Imbalance |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 4.7.2.1 (Cell balance) |
| Instructions | 1. Rest at 0 mA 2. Charge at 1 mA 3. Rest at 0 mA |
| Pass / Fail Criteria | ⦁ Prefault cell imbalance is False after resting at 0mA ⦁ Permanent disable cell imbalance is False after resting at 0mA ⦁ Prefault cell imbalance is True after resting at 1 A ⦁ Permanent disable cell imbalance is True after resting 1 A ⦁ Prefault cell imbalance is True after resting at 0 A ⦁ Permanent disable cell imbalance is False after resting at 0 A |
| Estimated Duration | 10 seconds |
| Note | Occurs when the difference between the highest and lowest cell is 0.5V. |
3200 def test_current_limit(self, serial_watcher: SerialWatcher): 3201 """ 3202 | Description | Confirm A fault is raised after 3.5A | 3203 | :------------------- | :----------------------------------------------------------------------------------- | 3204 | GitHub Issue | turnaroundfactor/HITL#476 | 3205 | Instructions | 1. Rest for 30 second </br>\ 3206 2. Charge at 3.75 amps (the max limit will be 3.5 amps) </br>\ 3207 3. Verify a prefault and fault are raised. </br>\ 3208 4. Rest until faults clear </br>\ 3209 5. Charge at 3.75 amps </br>\ 3210 6. Verify a prefault occurred but not a fault | 3211 | Pass / Fail Criteria | Pass if faults are raised | 3212 | Estimated Duration | 2 minutes | 3213 | Notes | We use 3.5A as a limit due to hardware limitations | 3214 """ 3215 3216 logger.write_info_to_report("Testing Current Limit") 3217 assert _bms.load and _bms.charger # Confirm hardware is available 3218 3219 # Rest and make sure no faults are active 3220 logger.write_result_to_html_report("Confirm no faults are active") 3221 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 1) 3222 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 1) 3223 3224 # Charge at 3.25 amps and verify faults are raised 3225 logger.write_result_to_html_report("Charging 3.75 A") 3226 with _bms.charger(16.8, 3.75): 3227 serial_watcher.assert_true("flags.prefault_overcurrent_charge", True, 2, wait_time=60) 3228 serial_watcher.assert_true("flags.fault_overcurrent_charge", True, 2, wait_time=60) 3229 3230 # Rest until faults clear 3231 logger.write_result_to_html_report("Resting") 3232 serial_watcher.assert_true("flags.prefault_overcurrent_charge", False, 3) 3233 serial_watcher.assert_true("flags.fault_overcurrent_charge", False, 3) 3234 assert ( 3235 serial_watcher.events["flags.fault_overcurrent_charge"][2].bms_time 3236 - serial_watcher.events["flags.fault_overcurrent_charge"][1].bms_time 3237 ).total_seconds() >= 60
| Description | Confirm A fault is raised after 3.5A |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#476 |
| Instructions | 1. Rest for 30 second 2. Charge at 3.75 amps (the max limit will be 3.5 amps) 3. Verify a prefault and fault are raised. 4. Rest until faults clear 5. Charge at 3.75 amps 6. Verify a prefault occurred but not a fault |
| Pass / Fail Criteria | Pass if faults are raised |
| Estimated Duration | 2 minutes |
| Notes | We use 3.5A as a limit due to hardware limitations |
3239 def test_undertemp_charge_rate(self, serial_watcher: SerialWatcher): 3240 """ 3241 | Description | Confirm a fault is raised at or above 3.1A when below 5°C | 3242 | :------------------- | :----------------------------------------------------------------------------------- | 3243 | GitHub Issue | turnaroundfactor/HITL#611 | 3244 | Instructions | 1. Rest for 30 second </br>\ 3245 2. Charge at 3.3 amps below 5°C </br>\ 3246 3. Verify a prefault (before 1 second) and fault (after 1 second) are raised. </br>\ 3247 4. Charge above 7°C until faults clear | 3248 | Pass / Fail Criteria | Pass if faults are raised | 3249 | Estimated Duration | 2 minutes | 3250 """ 3251 3252 logger.write_info_to_report("Testing Undertemp charge rate") 3253 assert _bms.load and _bms.charger # Confirm hardware is available 3254 3255 # Rest and make sure no faults are active 3256 logger.write_result_to_html_report("Confirm no faults are active") 3257 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 1) 3258 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 1) 3259 3260 # Discharge at 3.1+ amps and verify faults are raised 3261 logger.write_result_to_html_report("Discharging at 3.1A+") 3262 with _bms.charger(16.8, 3.3): 3263 _plateset.thermistor1 = 3 3264 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", True, 2, wait_time=60) 3265 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", True, 2, wait_time=60) 3266 _plateset.thermistor1 = 8 3267 serial_watcher.assert_true("flags.prefault_undertemp_charge_rate", False, 3, wait_time=90) 3268 serial_watcher.assert_true("flags.fault_undertemp_charge_rate", False, 3, wait_time=90)
| Description | Confirm a fault is raised at or above 3.1A when below 5°C |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#611 |
| Instructions | 1. Rest for 30 second 2. Charge at 3.3 amps below 5°C 3. Verify a prefault (before 1 second) and fault (after 1 second) are raised. 4. Charge above 7°C until faults clear |
| Pass / Fail Criteria | Pass if faults are raised |
| Estimated Duration | 2 minutes |
3270 def test_overcurrent_discharge_sustained(self, serial_watcher: SerialWatcher): 3271 """ 3272 | Description | Test Over Current Discharge Sustained Fault | 3273 | :------------------- | :--------------------------------------------------------------------- | 3274 | GitHub Issue | turnaroundfactor/HITL#762 | 3275 | Instructions | 1. Check prefault_sw_overcurrent_discharge and fault_sw_overcurrent_discharge </br>\ 3276 2. Set current to less than -2.5 amps </br>\ 3277 3. Confirm faults listed in #1 are true </br>\ 3278 4. Set current to 0 amps (rest) </br>\ 3279 5. Confirm faults listed in #1 are false | 3280 | Pass / Fail Criteria | ⦁ Prefault overCurrent Discharge Sustained is False at start </br>\ 3281 ⦁ Fault overCurrent Discharge Sustained is False at start </br>\ 3282 ⦁ Prefault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3283 ⦁ Fault overCurrent Discharge Sustained is True when current is -2.5 A </br>\ 3284 ⦁ Prefault overCurrent Discharge Sustained is False when current is 0 A </br>\ 3285 ⦁ Fault overCurrent Discharge Sustained is False when current is 0 A | 3286 | Estimated Duration | 10 seconds | 3287 """ 3288 3289 logger.write_info_to_report("Testing Overcurrent Discharge Sustained Faults") 3290 assert _bms.load and _bms.charger # Confirm hardware is available 3291 3292 logger.write_info_to_report("Sw overcurrent protection high current short time") 3293 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 1) 3294 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 1) 3295 3296 logger.write_result_to_html_report("Setting current to less than -2.6 amps") 3297 with _bms.load(2.6): 3298 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", True, 2) 3299 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", True, 2) 3300 3301 logger.write_result_to_html_report("Resting") 3302 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3303 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_2", False, 3, wait_time=60 * 25) 3304 3305 logger.write_info_to_report("Sw overcurrent protection low current longer time") 3306 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 1) 3307 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 1) 3308 3309 logger.write_result_to_html_report("Setting current to less than -2.4 amps") 3310 with _bms.load(2.4): 3311 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", True, 2) 3312 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", True, 2) 3313 3314 logger.write_result_to_html_report("Resting") 3315 serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10) 3316 serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge_1", False, 3, wait_time=60 * 10)
| Description | Test Over Current Discharge Sustained Fault |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#762 |
| Instructions | 1. Check prefault_sw_overcurrent_discharge and fault_sw_overcurrent_discharge 2. Set current to less than -2.5 amps 3. Confirm faults listed in #1 are true 4. Set current to 0 amps (rest) 5. Confirm faults listed in #1 are false |
| Pass / Fail Criteria | ⦁ Prefault overCurrent Discharge Sustained is False at start ⦁ Fault overCurrent Discharge Sustained is False at start ⦁ Prefault overCurrent Discharge Sustained is True when current is -2.5 A ⦁ Fault overCurrent Discharge Sustained is True when current is -2.5 A ⦁ Prefault overCurrent Discharge Sustained is False when current is 0 A ⦁ Fault overCurrent Discharge Sustained is False when current is 0 A |
| Estimated Duration | 10 seconds |
3319@pytest.mark.parametrize("reset_test_environment", [{"volts": 2.5}], indirect=True) 3320class TestStateOfCharge: 3321 """Confirm cell sim SOC and BMS SOC are within 5%""" 3322 3323 def test_state_of_charge(self): 3324 """ 3325 | Description | Confirm cell sim state of charge and BMS state of charge are within 5% | 3326 | :------------------- | :--------------------------------------------------------------------- | 3327 | GitHub Issue | turnaroundfactor/HITL#474 | 3328 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3329jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D19) | 3330 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 3331 | Instructions | 1. Set cell sims SOC to 5% </br>\ 3332 2. Confirm serial SOC is within 5% </br>\ 3333 3. Increment the cell sim SOC by 1% and repeat steps 1 & 2. | 3334 | Pass / Fail Criteria | ⦁ Serial state of charge is within 5% | 3335 | Estimated Duration | 20 seconds | 3336 | Note | When tested as specified in MIL-PERF section 4.7.2.15.1, SMBus data \ 3337 output shall be accurate within +0/-5% of the actual state of charge \ 3338 for the battery under test throughout the discharge. Manufacturer and \ 3339 battery data shall be correctly programmed (see 4.7.2.15.1). | 3340 """ 3341 3342 percent_failed = [] 3343 allowed_error = 0.05 3344 max_error = 0.0 3345 failure_rate = [] 3346 3347 for percent in range(5, 101): 3348 logger.write_info_to_report(f"Setting cell sims SOC to {percent}%") 3349 3350 for cell in _bms.cells.values(): 3351 cell.state_of_charge = percent / 100 3352 3353 time.sleep(2) 3354 serial_monitor.read() # Clear the latest serial buffer 3355 3356 serial_data = serial_monitor.read() 3357 percent_charged = serial_data["percent_charged"] 3358 3359 max_error = max(max_error, abs(percent_charged - percent)) 3360 if not (percent - 5) <= percent_charged <= (percent + 5): 3361 failure_rate.append(1) 3362 logger.write_warning_to_report( 3363 f"State of Charge is not within {allowed_error:.0%} of {percent}, received {percent_charged}%" 3364 ) 3365 percent_failed.append(str(percent)) 3366 else: 3367 failure_rate.append(0) 3368 logger.write_info_to_report( 3369 f"State of Charge is within {allowed_error:.0%} of {percent}% after changing cell sims SOC" 3370 ) 3371 3372 if len(percent_failed) > 0: 3373 message = ( 3374 f"SOC Error: {max_error / 100:.1%} ≰ {allowed_error:.0%} " 3375 f"[{sum(failure_rate) / len(failure_rate):.1%} failed]" 3376 ) 3377 logger.write_result_to_html_report(message) 3378 pytest.fail(message) 3379 else: 3380 logger.write_result_to_html_report(f"SOC Error: {max_error:.1%} ≤ {allowed_error:.0%}")
Confirm cell sim SOC and BMS SOC are within 5%
3323 def test_state_of_charge(self): 3324 """ 3325 | Description | Confirm cell sim state of charge and BMS state of charge are within 5% | 3326 | :------------------- | :--------------------------------------------------------------------- | 3327 | GitHub Issue | turnaroundfactor/HITL#474 | 3328 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3329jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D19) | 3330 | MIL-PRF Sections | 3.5.9.1 (SMBus) | 3331 | Instructions | 1. Set cell sims SOC to 5% </br>\ 3332 2. Confirm serial SOC is within 5% </br>\ 3333 3. Increment the cell sim SOC by 1% and repeat steps 1 & 2. | 3334 | Pass / Fail Criteria | ⦁ Serial state of charge is within 5% | 3335 | Estimated Duration | 20 seconds | 3336 | Note | When tested as specified in MIL-PERF section 4.7.2.15.1, SMBus data \ 3337 output shall be accurate within +0/-5% of the actual state of charge \ 3338 for the battery under test throughout the discharge. Manufacturer and \ 3339 battery data shall be correctly programmed (see 4.7.2.15.1). | 3340 """ 3341 3342 percent_failed = [] 3343 allowed_error = 0.05 3344 max_error = 0.0 3345 failure_rate = [] 3346 3347 for percent in range(5, 101): 3348 logger.write_info_to_report(f"Setting cell sims SOC to {percent}%") 3349 3350 for cell in _bms.cells.values(): 3351 cell.state_of_charge = percent / 100 3352 3353 time.sleep(2) 3354 serial_monitor.read() # Clear the latest serial buffer 3355 3356 serial_data = serial_monitor.read() 3357 percent_charged = serial_data["percent_charged"] 3358 3359 max_error = max(max_error, abs(percent_charged - percent)) 3360 if not (percent - 5) <= percent_charged <= (percent + 5): 3361 failure_rate.append(1) 3362 logger.write_warning_to_report( 3363 f"State of Charge is not within {allowed_error:.0%} of {percent}, received {percent_charged}%" 3364 ) 3365 percent_failed.append(str(percent)) 3366 else: 3367 failure_rate.append(0) 3368 logger.write_info_to_report( 3369 f"State of Charge is within {allowed_error:.0%} of {percent}% after changing cell sims SOC" 3370 ) 3371 3372 if len(percent_failed) > 0: 3373 message = ( 3374 f"SOC Error: {max_error / 100:.1%} ≰ {allowed_error:.0%} " 3375 f"[{sum(failure_rate) / len(failure_rate):.1%} failed]" 3376 ) 3377 logger.write_result_to_html_report(message) 3378 pytest.fail(message) 3379 else: 3380 logger.write_result_to_html_report(f"SOC Error: {max_error:.1%} ≤ {allowed_error:.0%}")
| Description | Confirm cell sim state of charge and BMS state of charge are within 5% |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#474 |
| Google Docs | Google Sheet Cell |
| MIL-PRF Sections | 3.5.9.1 (SMBus) |
| Instructions | 1. Set cell sims SOC to 5% 2. Confirm serial SOC is within 5% 3. Increment the cell sim SOC by 1% and repeat steps 1 & 2. |
| Pass / Fail Criteria | ⦁ Serial state of charge is within 5% |
| Estimated Duration | 20 seconds |
| Note | When tested as specified in MIL-PERF section 4.7.2.15.1, SMBus data output shall be accurate within +0/-5% of the actual state of charge for the battery under test throughout the discharge. Manufacturer and battery data shall be correctly programmed (see 4.7.2.15.1). |
3383@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3384class TestTemperatureAccuracy: 3385 """Compare BMS and HITL temps.""" 3386 3387 class TemperatureDiscrepancyTherm1(CSVRecordEvent): 3388 """@private Compare HITL temperature to reported temperature.""" 3389 3390 allowable_error = 5.0 3391 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 3392 3393 @classmethod 3394 def failed(cls) -> bool: 3395 """Check if test parameters were exceeded.""" 3396 return bool(cls.max.error > cls.allowable_error) 3397 3398 @classmethod 3399 def verify(cls, _row, serial_data, _cell_data): 3400 """Temperature within range""" 3401 row_data = SimpleNamespace(hitl_c=_plateset.thermistor1, bms_c=serial_data["dk_temp"] / 10 - 273) 3402 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 3403 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3404 3405 @classmethod 3406 def result(cls): 3407 """Detailed test result information.""" 3408 return ( 3409 f"Thermistor 1 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 3410 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 3411 ) 3412 3413 class TemperatureDiscrepancyTherm2(CSVRecordEvent): 3414 """@private Compare HITL temperature to reported temperature.""" 3415 3416 allowable_error = 5.0 3417 max = SimpleNamespace(hitl_c=0, bms_c=0, error=0.0) 3418 3419 @classmethod 3420 def failed(cls) -> bool: 3421 """Check if test parameters were exceeded.""" 3422 return bool(cls.max.error > cls.allowable_error) 3423 3424 @classmethod 3425 def verify(cls, _row, serial_data, _cell_data): 3426 """Temperature within range""" 3427 row_data = SimpleNamespace(hitl_c=_plateset.thermistor2, bms_c=serial_data["dk_temp1"] / 10 - 273) 3428 row_data.error = abs(row_data.bms_c - row_data.hitl_c) 3429 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3430 3431 @classmethod 3432 def result(cls): 3433 """Detailed test result information.""" 3434 return ( 3435 f"Thermistor 2 error: {cls.cmp(cls.max.error, '<=', cls.allowable_error, '°C', '.2f')}" 3436 f"(HITL: {cls.max.hitl_c:.2f} °C, BMS: {cls.max.bms_c:.2f} °C)" 3437 ) 3438 3439 def test_temperature_accuracy(self): 3440 """ 3441 | Description | TAF: Ensure that temperature measurements are accurate | 3442 | :------------------- | :--------------------------------------------------------------------------- | 3443 | GitHub Issue | turnaroundfactor/HITL#401 | 3444 | MIL-PRF Section | 3.5.8.3 (Accuracy) </br>\ 3445 4.7.2.14.3 (Accuracy During Discharge) | 3446 | Instructions | 1. Set THERM1 and THERM2 to -40C </br>\ 3447 2. Set cell voltages to 3.7V per cell </br>\ 3448 3. Increment THERM1 and THERM2 in 5C increments up to and including 60C </br>\ 3449 4. Record the following data at each current increment </br>\ 3450 HITL: THERM1 Measurement, THERM2 Measurement </br>\ 3451 SERIAL: THERM1, THERM2" | 3452 | Pass / Fail Criteria | HITL and Serial are within 5% | 3453 | Estimated Duration | 1 minute | 3454 """ 3455 _bms.timer.reset() # Keep track of runtime 3456 _plateset.disengage_safety_protocols = True 3457 3458 for target_c in range(-40, 65, 5): 3459 _plateset.thermistor1 = _plateset.thermistor2 = target_c 3460 time.sleep(1) 3461 logger.write_info_to_report( 3462 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, " 3463 f"Temp1: {_plateset.thermistor1}, Temp2: {_plateset.thermistor2}" 3464 ) 3465 _bms.csv.cycle.record(_bms.timer.elapsed_time) 3466 time.sleep(0.5) 3467 3468 # Check results 3469 if CSVRecordEvent.failed(): 3470 pytest.fail(CSVRecordEvent.result())
Compare BMS and HITL temps.
3439 def test_temperature_accuracy(self): 3440 """ 3441 | Description | TAF: Ensure that temperature measurements are accurate | 3442 | :------------------- | :--------------------------------------------------------------------------- | 3443 | GitHub Issue | turnaroundfactor/HITL#401 | 3444 | MIL-PRF Section | 3.5.8.3 (Accuracy) </br>\ 3445 4.7.2.14.3 (Accuracy During Discharge) | 3446 | Instructions | 1. Set THERM1 and THERM2 to -40C </br>\ 3447 2. Set cell voltages to 3.7V per cell </br>\ 3448 3. Increment THERM1 and THERM2 in 5C increments up to and including 60C </br>\ 3449 4. Record the following data at each current increment </br>\ 3450 HITL: THERM1 Measurement, THERM2 Measurement </br>\ 3451 SERIAL: THERM1, THERM2" | 3452 | Pass / Fail Criteria | HITL and Serial are within 5% | 3453 | Estimated Duration | 1 minute | 3454 """ 3455 _bms.timer.reset() # Keep track of runtime 3456 _plateset.disengage_safety_protocols = True 3457 3458 for target_c in range(-40, 65, 5): 3459 _plateset.thermistor1 = _plateset.thermistor2 = target_c 3460 time.sleep(1) 3461 logger.write_info_to_report( 3462 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, " 3463 f"Temp1: {_plateset.thermistor1}, Temp2: {_plateset.thermistor2}" 3464 ) 3465 _bms.csv.cycle.record(_bms.timer.elapsed_time) 3466 time.sleep(0.5) 3467 3468 # Check results 3469 if CSVRecordEvent.failed(): 3470 pytest.fail(CSVRecordEvent.result())
| Description | TAF: Ensure that temperature measurements are accurate |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#401 |
| MIL-PRF Section | 3.5.8.3 (Accuracy) 4.7.2.14.3 (Accuracy During Discharge) |
| Instructions | 1. Set THERM1 and THERM2 to -40C 2. Set cell voltages to 3.7V per cell 3. Increment THERM1 and THERM2 in 5C increments up to and including 60C 4. Record the following data at each current increment HITL: THERM1 Measurement, THERM2 Measurement SERIAL: THERM1, THERM2" |
| Pass / Fail Criteria | HITL and Serial are within 5% |
| Estimated Duration | 1 minute |
3473@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3474class TestCurrentAccuracy: 3475 """Run a test for current accuracy""" 3476 3477 class CurrentCellAccuracy(CSVRecordEvent): 3478 """@private Compare terminal current to reported current.""" 3479 3480 allowable_error = 0.01 3481 max = SimpleNamespace(hitl_a=0, bms_a=0, error=0.0) 3482 3483 @classmethod 3484 def failed(cls) -> bool: 3485 """Check if test parameters were exceeded.""" 3486 return bool(cls.max.error > cls.allowable_error) 3487 3488 @classmethod 3489 def verify(cls, row, serial_data, _cell_data): 3490 """Current within range""" 3491 if not _plateset.load_switch: 3492 row_data = SimpleNamespace(hitl_a=row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 3493 else: 3494 row_data = SimpleNamespace(hitl_a=-row["HITL Current (A)"], bms_a=serial_data["mamps"] / 1000) 3495 row_data.error = abs((row_data.bms_a - row_data.hitl_a) / row_data.hitl_a) 3496 if abs(row_data.hitl_a) > 0.100: # Ignore currents within 100mA to -100mA 3497 cls.max = max(cls.max, row_data, key=lambda data: data.error) 3498 3499 @classmethod 3500 def result(cls): 3501 """Detailed test result information.""" 3502 return ( 3503 f"Current error: {cls.cmp(cls.max.error, '<=', cls.allowable_error)} " 3504 f"(HITL: {cls.max.hitl_a * 1000:.3f} mA, BMS: {cls.max.bms_a * 1000:.3f} mA)" 3505 ) 3506 3507 def test_current_accuracy(self): 3508 """ 3509 | Description | Test the cell current accuracy | 3510 | :------------------- | :--------------------------------------------------------------------- | 3511 | GitHub Issue | turnaroundfactor/HITL#400 | 3512 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3513 4.7.2.14.3 (Accuracy During Discharge) | 3514 | Instructions | 1. Set thermistors to 23C | 3515 2. Set cell voltages to 3.7V per cell | 3516 3. Increment the charing current from 100mA to 3A in 50mA increments | 3517 4. Increment the discharging current from 100mA to 3A in 50 mA | 3518 increments | 3519 5. Record the following data at each current increment | 3520 HITL: Current (A) | 3521 SERIAL: Current (A) | 3522 | Pass / Fail Criteria | Pass IF: | 3523 SERIAL Current measurements agree with the HITL Terminal Current | 3524 measurements to within 1% for abs(Terminal Current >= 100mA) | 3525 - Result highest current (mA) discrepancy | 3526 | Estimated Duration | ?? | 3527 | Note | ?? | 3528 """ 3529 _bms.timer.reset() # Keep track of runtime 3530 3531 with _bms.charger(16.8, 0.1): 3532 for target_ma in range(100, 2050, 50): 3533 _bms.charger.amps = target_ma / 1000 3534 time.sleep(1) 3535 logger.write_info_to_report( 3536 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.charger.amps:.3f}" 3537 ) 3538 time.sleep(1) 3539 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.charger.amps) 3540 time.sleep(1) 3541 3542 with _bms.load(0.1): 3543 for target_ma in range(100, 2050, 50): 3544 time.sleep(1) 3545 _bms.load.amps = target_ma / 1000 3546 time.sleep(1) 3547 logger.write_info_to_report( 3548 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.load.amps:.3f}" 3549 ) 3550 time.sleep(1) 3551 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.load.amps) 3552 3553 if CSVRecordEvent.failed(): 3554 pytest.fail(CSVRecordEvent.result())
Run a test for current accuracy
3507 def test_current_accuracy(self): 3508 """ 3509 | Description | Test the cell current accuracy | 3510 | :------------------- | :--------------------------------------------------------------------- | 3511 | GitHub Issue | turnaroundfactor/HITL#400 | 3512 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3513 4.7.2.14.3 (Accuracy During Discharge) | 3514 | Instructions | 1. Set thermistors to 23C | 3515 2. Set cell voltages to 3.7V per cell | 3516 3. Increment the charing current from 100mA to 3A in 50mA increments | 3517 4. Increment the discharging current from 100mA to 3A in 50 mA | 3518 increments | 3519 5. Record the following data at each current increment | 3520 HITL: Current (A) | 3521 SERIAL: Current (A) | 3522 | Pass / Fail Criteria | Pass IF: | 3523 SERIAL Current measurements agree with the HITL Terminal Current | 3524 measurements to within 1% for abs(Terminal Current >= 100mA) | 3525 - Result highest current (mA) discrepancy | 3526 | Estimated Duration | ?? | 3527 | Note | ?? | 3528 """ 3529 _bms.timer.reset() # Keep track of runtime 3530 3531 with _bms.charger(16.8, 0.1): 3532 for target_ma in range(100, 2050, 50): 3533 _bms.charger.amps = target_ma / 1000 3534 time.sleep(1) 3535 logger.write_info_to_report( 3536 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.charger.amps:.3f}" 3537 ) 3538 time.sleep(1) 3539 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.charger.amps) 3540 time.sleep(1) 3541 3542 with _bms.load(0.1): 3543 for target_ma in range(100, 2050, 50): 3544 time.sleep(1) 3545 _bms.load.amps = target_ma / 1000 3546 time.sleep(1) 3547 logger.write_info_to_report( 3548 f"Elapsed Time: {_bms.timer.elapsed_time:.3f}, Current: {_bms.load.amps:.3f}" 3549 ) 3550 time.sleep(1) 3551 _bms.csv.cycle.record(_bms.timer.elapsed_time, _bms.load.amps) 3552 3553 if CSVRecordEvent.failed(): 3554 pytest.fail(CSVRecordEvent.result())
| Description | Test the cell current accuracy |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#400 |
| MIL-PRF Sections | 3.5.8.3 (Accuracy) 4.7.2.14.3 (Accuracy During Discharge) |
| Instructions | 1. Set thermistors to 23C |
2. Set cell voltages to 3.7V per cell | 3. Increment the charing current from 100mA to 3A in 50mA increments | 4. Increment the discharging current from 100mA to 3A in 50 mA | increments | 5. Record the following data at each current increment | HITL: Current (A) | SERIAL: Current (A) | | Pass / Fail Criteria | Pass IF: | SERIAL Current measurements agree with the HITL Terminal Current | measurements to within 1% for abs(Terminal Current >= 100mA) | - Result highest current (mA) discrepancy | | Estimated Duration | ?? | | Note | ?? |
3557def battery0_voltage_check(serial_data: dict[str, int | bool | str]): 3558 """Checks the SERIAL Battery 0 Voltage""" 3559 expected_charge = 14.8 3560 voltage_low_range = expected_charge - 0.100 3561 voltage_high_range = expected_charge + 2 3562 voltage_value = f"{expected_charge}V -100mV/+2V" 3563 3564 serial_voltage = float(serial_data["mvolt_battery"]) / 1000 3565 3566 if voltage_low_range <= serial_voltage <= voltage_high_range: 3567 logger.write_result_to_html_report( 3568 f"Battery 0 Voltage is {serial_voltage}V at the start of this test, " 3569 f"which is within within range of: {voltage_value}" 3570 ) 3571 3572 else: 3573 logger.write_failure_to_html_report( 3574 f"Battery 0 Voltage is {serial_voltage}V at the start of this test, " 3575 f"which is not within within range of: {voltage_value}" 3576 ) 3577 pytest.fail()
Checks the SERIAL Battery 0 Voltage
3580@pytest.mark.parametrize("reset_test_environment", [{"volts": 3.7, "temperature": 23}], indirect=True) 3581class TestCeaseCharging(CSVRecordEvent): 3582 """Run a test to cease charging after""" 3583 3584 def test_cease_charging(self): 3585 """ 3586 | Description | Cease charging if charger keeps trying too long | 3587 | :------------------- | :--------------------------------------------------------------------- | 3588 | GitHub Issue | turnaroundfactor/HITL#745 | 3589 #TODO: Update MIL-PRF sections 3590 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3591 4.7.2.14.3 (Accuracy During Discharge) | 3592 | Instructions | 1. Set thermistors to 23C </br>\ 3593 2. Put cells in a rested state at 3.7V per cell </br>\ 3594 3. Charge at 40 mA (do not let charger side terminate charge) </br>\ 3595 4. Wait 3,480 seconds (0:58 HR:MIN) </br>\ 3596 5. Wait 240 seconds (1:02 HR:MIN) </br>\ 3597 6. Disable CE </br>\ 3598 7. Wait 5 Seconds </br>\ 3599 8. Enable CE </br>\ 3600 9. Wait 5 seconds </br>\ 3601 10. Attempt to charge at 2A | 3602 | Pass / Fail Criteria | Pass IF at Step #... </br>\ 3603 1. SERIAL THERM1 and THERM2 are 23C +/- 1.1C </br>\ 3604 2. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3605 3. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3606 4. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3607 4. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3608 4. SERIAL No Fault Flags </br>\ 3609 5. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3610 5. HITL Charge Current is 0 mA +/- 1mA </br>\ 3611 5. SERIAL Flag OverTime_Charge is flagged </br>\ 3612 10. HITL Charge Current is 2A +/- 30mA | 3613 | Estimated Duration | 64 minutes | 3614 | Note | ?? | 3615 """ 3616 serial_data = serial_monitor.read() 3617 3618 set_temp = 23 3619 # Check THERM 1 & Therm 2 3620 therm_one = serial_data["dk_temp"] / 10 - 273 3621 therm_two = serial_data["dk_temp1"] / 10 - 273 3622 low_range = set_temp - 1.1 3623 high_range = set_temp + 1.1 3624 temp_range = f"{set_temp}°C +/- 1.1°C" 3625 3626 if low_range <= therm_one <= high_range: 3627 logger.write_result_to_html_report( 3628 f"THERM1 was {therm_one:.1f}°C, which was within the expected range of {temp_range}" 3629 ) 3630 else: 3631 logger.write_failure_to_html_report( 3632 f"THERM1 was {therm_one:.1f}°C, which was not within the expected range of {temp_range}" 3633 ) 3634 pytest.fail() 3635 3636 if low_range <= therm_two <= high_range: 3637 logger.write_result_to_html_report( 3638 f"THERM2 was {therm_two:.1f}°C, which was within the expected range of {temp_range}" 3639 ) 3640 else: 3641 logger.write_failure_to_html_report( 3642 f"THERM2 was {therm_two:.1f}°C, which was not within the expected range of {temp_range}" 3643 ) 3644 pytest.fail() 3645 3646 # Check Serial Battery 0 Voltage: 3647 battery0_voltage_check(serial_data) 3648 3649 # Charge at 40 mA -- keep voltage at 3.7 3650 with _bms.charger(16.8, 0.040): 3651 logger.write_info_to_report("Charging at 40mA") 3652 time.sleep(60) 3653 3654 high_current_range = 0.020 + 0.070 3655 low_current_range = 0.020 3656 3657 expected_current_range = "20mA +70mA/-0mA" 3658 terminal_current = _bms.charger.amps 3659 3660 if low_current_range <= terminal_current <= high_current_range: 3661 logger.write_result_to_html_report( 3662 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was within the expected " 3663 f"range of {expected_current_range}" 3664 ) 3665 else: 3666 logger.write_failure_to_html_report( 3667 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was not within the " 3668 f"expected range of {expected_current_range}" 3669 ) 3670 pytest.fail() 3671 3672 # 1 hour (debug mode) 3673 long_rest = 3480 3674 3675 logger.write_info_to_report(f"Waiting for {long_rest} seconds") 3676 time.sleep(long_rest) 3677 3678 serial_data = serial_monitor.read() 3679 3680 # Check Battery 0 Voltage 3681 battery0_voltage_check(serial_data) 3682 3683 # Check HITL Charge Current 3684 terminal_current = _bms.charger.amps 3685 3686 if low_current_range <= terminal_current <= high_current_range: 3687 logger.write_result_to_html_report( 3688 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3689 f"which was within the expected range of {expected_current_range}" 3690 ) 3691 else: 3692 logger.write_failure_to_html_report( 3693 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3694 f"which was not within the expected range of {expected_current_range}" 3695 ) 3696 pytest.fail() 3697 3698 # Check Serial OverTime_Charge 3699 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3700 if no_fault_flag is False: 3701 logger.write_result_to_html_report( 3702 f"OverTime Charge Fault was False, the expected value after waiting {long_rest} seconds" 3703 ) 3704 else: 3705 logger.write_failure_to_html_report( 3706 f"OverTime Charge Fault was True, which was not expected after waiting {long_rest} seconds" 3707 ) 3708 pytest.fail() 3709 3710 # Sleep for 240 seconds 3711 short_rest = 240 3712 logger.write_info_to_report(f"Waiting for {short_rest} seconds") 3713 time.sleep(short_rest) 3714 3715 serial_data = serial_monitor.read() 3716 3717 # Check Battery 0 Voltage 3718 battery0_voltage_check(serial_data) 3719 3720 # Check HITL Charge Current 3721 expected_charge = 0 3722 high_current_range = expected_charge + 0.020 3723 low_current_range = expected_charge - 0.020 3724 3725 expected_current_range = "0mA +/- 20mA" 3726 terminal_current = _bms.charger.amps 3727 3728 if low_current_range <= terminal_current <= high_current_range: 3729 logger.write_result_to_html_report( 3730 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3731 f"which was within the expected range of {expected_current_range}" 3732 ) 3733 else: 3734 logger.write_failure_to_html_report( 3735 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3736 f"which was not within the expected range of {expected_current_range}" 3737 ) 3738 pytest.fail() 3739 3740 # Check Overtime Charge Flag 3741 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3742 if no_fault_flag is True: 3743 logger.write_result_to_html_report( 3744 f"Overtime Charge Fault was True, the expected value after waiting {short_rest} seconds" 3745 ) 3746 else: 3747 logger.write_failure_to_html_report( 3748 f"Overtime Charge Fault was False, which was not expected after waiting {short_rest} seconds" 3749 ) 3750 pytest.fail() 3751 3752 # Wait 5 Seconds (Since BMS_Charger is disabled) 3753 time.sleep(5) 3754 3755 # Enable BMS Charger & Wait 5 Seconds before attempting to charge 3756 with _bms.charger(16.8, 2): 3757 time.sleep(5) 3758 3759 # Check HITL Charge Current 3760 terminal_current = _bms.charger.amps 3761 expected_current = 2 3762 expected_current_range = "2A +/- 30mA" 3763 high_current_range = expected_current + 0.03 3764 low_current_range = expected_current 3765 3766 if low_current_range <= terminal_current <= high_current_range: 3767 logger.write_result_to_html_report( 3768 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3769 f"which was within the expected range of {expected_current_range}" 3770 ) 3771 else: 3772 logger.write_failure_to_html_report( 3773 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3774 f"which was not within the expected range of {expected_current_range}" 3775 ) 3776 pytest.fail()
Run a test to cease charging after
3584 def test_cease_charging(self): 3585 """ 3586 | Description | Cease charging if charger keeps trying too long | 3587 | :------------------- | :--------------------------------------------------------------------- | 3588 | GitHub Issue | turnaroundfactor/HITL#745 | 3589 #TODO: Update MIL-PRF sections 3590 | MIL-PRF Sections | 3.5.8.3 (Accuracy) </br>\ 3591 4.7.2.14.3 (Accuracy During Discharge) | 3592 | Instructions | 1. Set thermistors to 23C </br>\ 3593 2. Put cells in a rested state at 3.7V per cell </br>\ 3594 3. Charge at 40 mA (do not let charger side terminate charge) </br>\ 3595 4. Wait 3,480 seconds (0:58 HR:MIN) </br>\ 3596 5. Wait 240 seconds (1:02 HR:MIN) </br>\ 3597 6. Disable CE </br>\ 3598 7. Wait 5 Seconds </br>\ 3599 8. Enable CE </br>\ 3600 9. Wait 5 seconds </br>\ 3601 10. Attempt to charge at 2A | 3602 | Pass / Fail Criteria | Pass IF at Step #... </br>\ 3603 1. SERIAL THERM1 and THERM2 are 23C +/- 1.1C </br>\ 3604 2. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3605 3. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3606 4. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3607 4. HITL Charge Current is 20 mA +70mA / -0mA </br>\ 3608 4. SERIAL No Fault Flags </br>\ 3609 5. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV </br>\ 3610 5. HITL Charge Current is 0 mA +/- 1mA </br>\ 3611 5. SERIAL Flag OverTime_Charge is flagged </br>\ 3612 10. HITL Charge Current is 2A +/- 30mA | 3613 | Estimated Duration | 64 minutes | 3614 | Note | ?? | 3615 """ 3616 serial_data = serial_monitor.read() 3617 3618 set_temp = 23 3619 # Check THERM 1 & Therm 2 3620 therm_one = serial_data["dk_temp"] / 10 - 273 3621 therm_two = serial_data["dk_temp1"] / 10 - 273 3622 low_range = set_temp - 1.1 3623 high_range = set_temp + 1.1 3624 temp_range = f"{set_temp}°C +/- 1.1°C" 3625 3626 if low_range <= therm_one <= high_range: 3627 logger.write_result_to_html_report( 3628 f"THERM1 was {therm_one:.1f}°C, which was within the expected range of {temp_range}" 3629 ) 3630 else: 3631 logger.write_failure_to_html_report( 3632 f"THERM1 was {therm_one:.1f}°C, which was not within the expected range of {temp_range}" 3633 ) 3634 pytest.fail() 3635 3636 if low_range <= therm_two <= high_range: 3637 logger.write_result_to_html_report( 3638 f"THERM2 was {therm_two:.1f}°C, which was within the expected range of {temp_range}" 3639 ) 3640 else: 3641 logger.write_failure_to_html_report( 3642 f"THERM2 was {therm_two:.1f}°C, which was not within the expected range of {temp_range}" 3643 ) 3644 pytest.fail() 3645 3646 # Check Serial Battery 0 Voltage: 3647 battery0_voltage_check(serial_data) 3648 3649 # Charge at 40 mA -- keep voltage at 3.7 3650 with _bms.charger(16.8, 0.040): 3651 logger.write_info_to_report("Charging at 40mA") 3652 time.sleep(60) 3653 3654 high_current_range = 0.020 + 0.070 3655 low_current_range = 0.020 3656 3657 expected_current_range = "20mA +70mA/-0mA" 3658 terminal_current = _bms.charger.amps 3659 3660 if low_current_range <= terminal_current <= high_current_range: 3661 logger.write_result_to_html_report( 3662 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was within the expected " 3663 f"range of {expected_current_range}" 3664 ) 3665 else: 3666 logger.write_failure_to_html_report( 3667 f"HITL Terminal Current was {terminal_current:.3f}A after charging, which was not within the " 3668 f"expected range of {expected_current_range}" 3669 ) 3670 pytest.fail() 3671 3672 # 1 hour (debug mode) 3673 long_rest = 3480 3674 3675 logger.write_info_to_report(f"Waiting for {long_rest} seconds") 3676 time.sleep(long_rest) 3677 3678 serial_data = serial_monitor.read() 3679 3680 # Check Battery 0 Voltage 3681 battery0_voltage_check(serial_data) 3682 3683 # Check HITL Charge Current 3684 terminal_current = _bms.charger.amps 3685 3686 if low_current_range <= terminal_current <= high_current_range: 3687 logger.write_result_to_html_report( 3688 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3689 f"which was within the expected range of {expected_current_range}" 3690 ) 3691 else: 3692 logger.write_failure_to_html_report( 3693 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {long_rest} seconds, " 3694 f"which was not within the expected range of {expected_current_range}" 3695 ) 3696 pytest.fail() 3697 3698 # Check Serial OverTime_Charge 3699 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3700 if no_fault_flag is False: 3701 logger.write_result_to_html_report( 3702 f"OverTime Charge Fault was False, the expected value after waiting {long_rest} seconds" 3703 ) 3704 else: 3705 logger.write_failure_to_html_report( 3706 f"OverTime Charge Fault was True, which was not expected after waiting {long_rest} seconds" 3707 ) 3708 pytest.fail() 3709 3710 # Sleep for 240 seconds 3711 short_rest = 240 3712 logger.write_info_to_report(f"Waiting for {short_rest} seconds") 3713 time.sleep(short_rest) 3714 3715 serial_data = serial_monitor.read() 3716 3717 # Check Battery 0 Voltage 3718 battery0_voltage_check(serial_data) 3719 3720 # Check HITL Charge Current 3721 expected_charge = 0 3722 high_current_range = expected_charge + 0.020 3723 low_current_range = expected_charge - 0.020 3724 3725 expected_current_range = "0mA +/- 20mA" 3726 terminal_current = _bms.charger.amps 3727 3728 if low_current_range <= terminal_current <= high_current_range: 3729 logger.write_result_to_html_report( 3730 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3731 f"which was within the expected range of {expected_current_range}" 3732 ) 3733 else: 3734 logger.write_failure_to_html_report( 3735 f"HITL Terminal Current was {terminal_current:.3f}A after waiting {short_rest} seconds, " 3736 f"which was not within the expected range of {expected_current_range}" 3737 ) 3738 pytest.fail() 3739 3740 # Check Overtime Charge Flag 3741 no_fault_flag = serial_data["flags.fault_overtime_charge"] 3742 if no_fault_flag is True: 3743 logger.write_result_to_html_report( 3744 f"Overtime Charge Fault was True, the expected value after waiting {short_rest} seconds" 3745 ) 3746 else: 3747 logger.write_failure_to_html_report( 3748 f"Overtime Charge Fault was False, which was not expected after waiting {short_rest} seconds" 3749 ) 3750 pytest.fail() 3751 3752 # Wait 5 Seconds (Since BMS_Charger is disabled) 3753 time.sleep(5) 3754 3755 # Enable BMS Charger & Wait 5 Seconds before attempting to charge 3756 with _bms.charger(16.8, 2): 3757 time.sleep(5) 3758 3759 # Check HITL Charge Current 3760 terminal_current = _bms.charger.amps 3761 expected_current = 2 3762 expected_current_range = "2A +/- 30mA" 3763 high_current_range = expected_current + 0.03 3764 low_current_range = expected_current 3765 3766 if low_current_range <= terminal_current <= high_current_range: 3767 logger.write_result_to_html_report( 3768 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3769 f"which was within the expected range of {expected_current_range}" 3770 ) 3771 else: 3772 logger.write_failure_to_html_report( 3773 f"HITL Terminal Current was {terminal_current:.3f}A after disabling/enabling charge, " 3774 f"which was not within the expected range of {expected_current_range}" 3775 ) 3776 pytest.fail()
| Description | Cease charging if charger keeps trying too long |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#745 |
TODO: Update MIL-PRF sections
| MIL-PRF Sections | 3.5.8.3 (Accuracy) 4.7.2.14.3 (Accuracy During Discharge) | | Instructions | 1. Set thermistors to 23C 2. Put cells in a rested state at 3.7V per cell 3. Charge at 40 mA (do not let charger side terminate charge) 4. Wait 3,480 seconds (0:58 HR:MIN) 5. Wait 240 seconds (1:02 HR:MIN) 6. Disable CE 7. Wait 5 Seconds 8. Enable CE 9. Wait 5 seconds 10. Attempt to charge at 2A | | Pass / Fail Criteria | Pass IF at Step #... 1. SERIAL THERM1 and THERM2 are 23C +/- 1.1C 2. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV 3. HITL Charge Current is 20 mA +70mA / -0mA 4. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV 4. HITL Charge Current is 20 mA +70mA / -0mA 4. SERIAL No Fault Flags 5. SERIAL Battery 0 Voltage is 14.8V +/- 100 mV 5. HITL Charge Current is 0 mA +/- 1mA 5. SERIAL Flag OverTime_Charge is flagged 10. HITL Charge Current is 2A +/- 30mA | | Estimated Duration | 64 minutes | | Note | ?? |
3779class TestColdTemperatureCharging(CSVRecordEvent): 3780 """Run a test for cold charging.""" 3781 3782 def test_cold_temperature_charging(self): 3783 """ 3784 | Description | Cold temperature charging | 3785 | :------------------- | :--------------------------------------------------------------------- | 3786 | GitHub Issue | turnaroundfactor/HITL#609 | 3787 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3788jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D42) | 3789 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3790 2. Put cells in rested state at 3.7V per cell </br>\ 3791 4. Attempt to charge at 3.2A </br>\ 3792 6. Set THERM1 and THERM2 to 0°C </br>\ 3793 7. Attempt to charge at 3.2A </br>\ 3794 7. Wait 5 seconds </br>\ 3795 7. Disable charging </br>\ 3796 7. Wait 65 seconds </br>\ 3797 7. Set THERM1 and THERM2 to 7°C </br>\ 3798 8. Attempt to charge at 3.2A | 3799 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3800 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA </br>\ 3801 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3802 ⦁ Expect HITL Terminal Current to be 0A +/- 30mA </br>\ 3803 ⦁ Expect Serial THERM1 & THERM 2 to be 7°C +/- 1.1°C </br>\ 3804 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA | 3805 | Estimated Duration | 17 seconds | 3806 """ 3807 3808 failed_tests = [] 3809 temperatures = [23, 0, 7] 3810 3811 for set_temp in temperatures: 3812 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 3813 3814 _plateset.disengage_safety_protocols = True 3815 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 3816 _plateset.disengage_safety_protocols = False 3817 3818 time.sleep(2) 3819 3820 # Get the serial data 3821 serial_data = serial_monitor.read() 3822 3823 # Convert temperature to Celsius from Kelvin 3824 therm_one = serial_data["dk_temp"] / 10 - 273 3825 therm_two = serial_data["dk_temp1"] / 10 - 273 3826 temp_range = f"{set_temp}°C +/- 1.1°C" 3827 low_range = set_temp - 1.1 3828 high_range = set_temp + 1.1 3829 3830 if low_range <= therm_one <= high_range: 3831 logger.write_result_to_html_report( 3832 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3833 ) 3834 else: 3835 logger.write_result_to_html_report( 3836 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3837 f"of {temp_range}</font>" 3838 ) 3839 failed_tests.append("THERM1") 3840 3841 if low_range <= therm_two <= high_range: 3842 logger.write_result_to_html_report( 3843 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3844 ) 3845 else: 3846 logger.write_result_to_html_report( 3847 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3848 f"expected range of {temp_range}</font>" 3849 ) 3850 failed_tests.append("THERM2") 3851 3852 logger.write_info_to_report("Attempting to charge at 1A") 3853 limit = 0.030 3854 charge_current = 3.2 3855 expected_current_range = f"3.2A +/- {limit}A" 3856 with _bms.charger(16.8, charge_current): 3857 time.sleep(1) 3858 if set_temp == 0: 3859 expected_current_range = f"0A +/- {limit}A" 3860 charge_current = 0 3861 charger_amps = _bms.charger.amps 3862 if charge_current - limit <= charger_amps <= charge_current + limit: 3863 logger.write_result_to_html_report( 3864 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 3865 f"expected range of {expected_current_range}" 3866 ) 3867 else: 3868 logger.write_result_to_html_report( 3869 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 3870 f"which was not within the expected range of {expected_current_range} </font>" 3871 ) 3872 failed_tests.append("HITL Terminal Current") 3873 3874 if set_temp == 0: 3875 time.sleep(5) 3876 if set_temp == 0: 3877 time.sleep(65) 3878 3879 if len(failed_tests) > 0: 3880 pytest.fail() 3881 3882 logger.write_result_to_html_report("All checks passed test")
Run a test for cold charging.
3782 def test_cold_temperature_charging(self): 3783 """ 3784 | Description | Cold temperature charging | 3785 | :------------------- | :--------------------------------------------------------------------- | 3786 | GitHub Issue | turnaroundfactor/HITL#609 | 3787 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3788jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D42) | 3789 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3790 2. Put cells in rested state at 3.7V per cell </br>\ 3791 4. Attempt to charge at 3.2A </br>\ 3792 6. Set THERM1 and THERM2 to 0°C </br>\ 3793 7. Attempt to charge at 3.2A </br>\ 3794 7. Wait 5 seconds </br>\ 3795 7. Disable charging </br>\ 3796 7. Wait 65 seconds </br>\ 3797 7. Set THERM1 and THERM2 to 7°C </br>\ 3798 8. Attempt to charge at 3.2A | 3799 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3800 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA </br>\ 3801 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3802 ⦁ Expect HITL Terminal Current to be 0A +/- 30mA </br>\ 3803 ⦁ Expect Serial THERM1 & THERM 2 to be 7°C +/- 1.1°C </br>\ 3804 ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA | 3805 | Estimated Duration | 17 seconds | 3806 """ 3807 3808 failed_tests = [] 3809 temperatures = [23, 0, 7] 3810 3811 for set_temp in temperatures: 3812 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {set_temp}°C") 3813 3814 _plateset.disengage_safety_protocols = True 3815 _plateset.thermistor1 = _plateset.thermistor2 = set_temp 3816 _plateset.disengage_safety_protocols = False 3817 3818 time.sleep(2) 3819 3820 # Get the serial data 3821 serial_data = serial_monitor.read() 3822 3823 # Convert temperature to Celsius from Kelvin 3824 therm_one = serial_data["dk_temp"] / 10 - 273 3825 therm_two = serial_data["dk_temp1"] / 10 - 273 3826 temp_range = f"{set_temp}°C +/- 1.1°C" 3827 low_range = set_temp - 1.1 3828 high_range = set_temp + 1.1 3829 3830 if low_range <= therm_one <= high_range: 3831 logger.write_result_to_html_report( 3832 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3833 ) 3834 else: 3835 logger.write_result_to_html_report( 3836 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3837 f"of {temp_range}</font>" 3838 ) 3839 failed_tests.append("THERM1") 3840 3841 if low_range <= therm_two <= high_range: 3842 logger.write_result_to_html_report( 3843 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3844 ) 3845 else: 3846 logger.write_result_to_html_report( 3847 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3848 f"expected range of {temp_range}</font>" 3849 ) 3850 failed_tests.append("THERM2") 3851 3852 logger.write_info_to_report("Attempting to charge at 1A") 3853 limit = 0.030 3854 charge_current = 3.2 3855 expected_current_range = f"3.2A +/- {limit}A" 3856 with _bms.charger(16.8, charge_current): 3857 time.sleep(1) 3858 if set_temp == 0: 3859 expected_current_range = f"0A +/- {limit}A" 3860 charge_current = 0 3861 charger_amps = _bms.charger.amps 3862 if charge_current - limit <= charger_amps <= charge_current + limit: 3863 logger.write_result_to_html_report( 3864 f"HITL Terminal Current was {charger_amps:.3f}A after charging, which was within the " 3865 f"expected range of {expected_current_range}" 3866 ) 3867 else: 3868 logger.write_result_to_html_report( 3869 f'<font color="#990000">HITL Terminal Current was {charger_amps:.3f}A after charging, ' 3870 f"which was not within the expected range of {expected_current_range} </font>" 3871 ) 3872 failed_tests.append("HITL Terminal Current") 3873 3874 if set_temp == 0: 3875 time.sleep(5) 3876 if set_temp == 0: 3877 time.sleep(65) 3878 3879 if len(failed_tests) > 0: 3880 pytest.fail() 3881 3882 logger.write_result_to_html_report("All checks passed test")
| Description | Cold temperature charging |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#609 |
| Google Docs | Google Sheet Cell |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 3.7V per cell 4. Attempt to charge at 3.2A 6. Set THERM1 and THERM2 to 0°C 7. Attempt to charge at 3.2A 7. Wait 5 seconds 7. Disable charging 7. Wait 65 seconds 7. Set THERM1 and THERM2 to 7°C 8. Attempt to charge at 3.2A |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be 0A +/- 30mA ⦁ Expect Serial THERM1 & THERM 2 to be 7°C +/- 1.1°C ⦁ Expect HITL Terminal Current to be 3.2A +/- 30mA |
| Estimated Duration | 17 seconds |
3885class TestColdTemperatureCurrent(CSVRecordEvent): 3886 """Run a test for cold current.""" 3887 3888 def set_temperature(self, celsius: float) -> bool: 3889 """Set and check the temperature.""" 3890 test_failed = False 3891 3892 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {celsius}°C") 3893 3894 _plateset.disengage_safety_protocols = True 3895 _plateset.thermistor1 = _plateset.thermistor2 = celsius 3896 _plateset.disengage_safety_protocols = False 3897 3898 time.sleep(2) 3899 3900 # Get the serial data 3901 serial_data = serial_monitor.read() 3902 assert serial_data, "No serial data recieved." 3903 3904 # Convert temperature to Celsius from Kelvin 3905 therm_one = int(serial_data["dk_temp"]) / 10 - 273 3906 therm_two = int(serial_data["dk_temp1"]) / 10 - 273 3907 temp_range = f"{celsius}°C +/- 1.1°C" 3908 low_range = celsius - 1.1 3909 high_range = celsius + 1.1 3910 3911 if low_range <= therm_one <= high_range: 3912 logger.write_result_to_html_report( 3913 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3914 ) 3915 else: 3916 logger.write_result_to_html_report( 3917 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3918 f"of {temp_range}</font>" 3919 ) 3920 test_failed = True 3921 3922 if low_range <= therm_two <= high_range: 3923 logger.write_result_to_html_report( 3924 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3925 ) 3926 else: 3927 logger.write_result_to_html_report( 3928 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3929 f"expected range of {temp_range}</font>" 3930 ) 3931 test_failed = True 3932 return test_failed 3933 3934 def test_cold_temperature_current(self): 3935 """ 3936 | Description | Cold temperature current | 3937 | :------------------- | :--------------------------------------------------------------------- | 3938 | GitHub Issue | turnaroundfactor/HITL#609 | 3939 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3940jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D43) | 3941 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3942 2. Put cells in rested state at 3.7V per cell </br>\ 3943 3. Read SMBus charging current </br>\ 3944 4. Set THERM1 and THERM2 to 0°C </br>\ 3945 5. Read SMBus charging current | 3946 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3947 ⦁ Expect SMBus charging current 0x07D0 (2000 mA) </br>\ 3948 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3949 ⦁ Expect SMBus charging current 0x03EB (1000 mA) | 3950 | Estimated Duration | 17 seconds | 3951 | Note | 0x07D0 (Note: This is the default value for debug build, Production \ 3952 build should return 0x1770) | 3953 """ 3954 3955 for temperature, expected_value in ((23, 2000), (0, 1000)): 3956 test_failed = self.set_temperature(temperature) 3957 time.sleep(5) 3958 charging_current = _smbus.read_register(SMBusReg.CHARGING_CURRENT) 3959 if charging_current[0] == expected_value: 3960 logger.write_result_to_html_report(f"Charging current: {charging_current[0]} = {expected_value}") 3961 else: 3962 logger.write_failure_to_html_report(f"Charging current: {charging_current[0]} ≠ {expected_value}") 3963 test_failed = True 3964 3965 if test_failed: 3966 pytest.fail()
Run a test for cold current.
3888 def set_temperature(self, celsius: float) -> bool: 3889 """Set and check the temperature.""" 3890 test_failed = False 3891 3892 logger.write_info_to_report(f"Setting THERM1 & THERM2 to {celsius}°C") 3893 3894 _plateset.disengage_safety_protocols = True 3895 _plateset.thermistor1 = _plateset.thermistor2 = celsius 3896 _plateset.disengage_safety_protocols = False 3897 3898 time.sleep(2) 3899 3900 # Get the serial data 3901 serial_data = serial_monitor.read() 3902 assert serial_data, "No serial data recieved." 3903 3904 # Convert temperature to Celsius from Kelvin 3905 therm_one = int(serial_data["dk_temp"]) / 10 - 273 3906 therm_two = int(serial_data["dk_temp1"]) / 10 - 273 3907 temp_range = f"{celsius}°C +/- 1.1°C" 3908 low_range = celsius - 1.1 3909 high_range = celsius + 1.1 3910 3911 if low_range <= therm_one <= high_range: 3912 logger.write_result_to_html_report( 3913 f"THERM1 was {therm_one:.1f}°C, which was within the expected temperature range of {temp_range}" 3914 ) 3915 else: 3916 logger.write_result_to_html_report( 3917 f'<font color="#990000">THERM1 was {therm_one:.1f}°C, which was not within the expected range ' 3918 f"of {temp_range}</font>" 3919 ) 3920 test_failed = True 3921 3922 if low_range <= therm_two <= high_range: 3923 logger.write_result_to_html_report( 3924 f"THERM2 was {therm_two:.1f}°C, which was within the expected temperature range of {temp_range}" 3925 ) 3926 else: 3927 logger.write_result_to_html_report( 3928 f'<font color="#990000">THERM2 was {therm_one:.1f}°C, which was not within the ' 3929 f"expected range of {temp_range}</font>" 3930 ) 3931 test_failed = True 3932 return test_failed
Set and check the temperature.
3934 def test_cold_temperature_current(self): 3935 """ 3936 | Description | Cold temperature current | 3937 | :------------------- | :--------------------------------------------------------------------- | 3938 | GitHub Issue | turnaroundfactor/HITL#609 | 3939 | Google Docs | [Google Sheet Cell](https://docs.google.com/spreadsheets/d/1r5A-g2twpNj\ 3940jZx_BjjZk90XgvQdicU9yoG9foicz_Nk/edit?gid=2093042698#gid=2093042698&range=D43) | 3941 | Instructions | 1. Set THERM1 and THERM2 to 23°C </br>\ 3942 2. Put cells in rested state at 3.7V per cell </br>\ 3943 3. Read SMBus charging current </br>\ 3944 4. Set THERM1 and THERM2 to 0°C </br>\ 3945 5. Read SMBus charging current | 3946 | Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C </br>\ 3947 ⦁ Expect SMBus charging current 0x07D0 (2000 mA) </br>\ 3948 ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C </br>\ 3949 ⦁ Expect SMBus charging current 0x03EB (1000 mA) | 3950 | Estimated Duration | 17 seconds | 3951 | Note | 0x07D0 (Note: This is the default value for debug build, Production \ 3952 build should return 0x1770) | 3953 """ 3954 3955 for temperature, expected_value in ((23, 2000), (0, 1000)): 3956 test_failed = self.set_temperature(temperature) 3957 time.sleep(5) 3958 charging_current = _smbus.read_register(SMBusReg.CHARGING_CURRENT) 3959 if charging_current[0] == expected_value: 3960 logger.write_result_to_html_report(f"Charging current: {charging_current[0]} = {expected_value}") 3961 else: 3962 logger.write_failure_to_html_report(f"Charging current: {charging_current[0]} ≠ {expected_value}") 3963 test_failed = True 3964 3965 if test_failed: 3966 pytest.fail()
| Description | Cold temperature current |
|---|---|
| GitHub Issue | turnaroundfactor/HITL#609 |
| Google Docs | Google Sheet Cell |
| Instructions | 1. Set THERM1 and THERM2 to 23°C 2. Put cells in rested state at 3.7V per cell 3. Read SMBus charging current 4. Set THERM1 and THERM2 to 0°C 5. Read SMBus charging current |
| Pass / Fail Criteria | ⦁ Expect Serial THERM1 & THERM 2 to be 23°C +/- 1.1°C ⦁ Expect SMBus charging current 0x07D0 (2000 mA) ⦁ Expect Serial THERM1 & THERM 2 to be 0°C +/- 1.1°C ⦁ Expect SMBus charging current 0x03EB (1000 mA) |
| Estimated Duration | 17 seconds |
| Note | 0x07D0 (Note: This is the default value for debug build, Production build should return 0x1770) |