hitl_tester.test_cases.bms.smart_charger_cycles
| Test | Smart Charger |
|---|---|
| GitHub Issue(s) | turnaroundfactor/HITL#314 |
| Description | Tests that our BB2590 works on a smart charger |
Used in these test plans:
- smart_charger_cycles_b ⠀⠀⠀(bms/smart_charger_cycles_b.plan)
- smart_charger_cycles_a ⠀⠀⠀(bms/smart_charger_cycles_a.plan)
Example Command (warning: test plan may run other test cases):
./hitl_tester.py smart_charger_cycles_b -DSAMPLE_TIME=10 -DEXPECTED_CURRENT=2.0 -DFAILED={'current': [0, 0], 'soc': [0, 0], 'cycle': [0, 0]}
1""" 2| Test | Smart Charger | 3| :------------------- | :------------------------------------------------------------------ | 4| GitHub Issue(s) | turnaroundfactor/HITL#314 | 5| Description | Tests that our BB2590 works on a smart charger | 6""" 7 8from __future__ import annotations 9 10import time 11from contextlib import contextmanager 12 13import pytest 14 15from hitl_tester.modules.bms.bms_hw import BMSHardware 16from hitl_tester.modules.bms.bms_serial import serial_monitor 17from hitl_tester.modules.bms.i2c_driver import I2CFileSniffer 18from hitl_tester.modules.bms.plateset import Plateset 19from hitl_tester.modules.logger import logger 20 21SAMPLE_TIME = 10 22EXPECTED_CURRENT = 2.0 23 24_bms = BMSHardware(pytest.flags) # type: ignore[arg-type] 25_bms.init() 26_plateset = Plateset() 27_i2c_file_sniffer = I2CFileSniffer() 28 29FAILED = {"current": [0, 0], "soc": [0, 0], "cycle": [0, 0]} 30 31 32class FailureLog: 33 """Monitor how successful a test is from 0 (pass) to 1 (fail).""" 34 35 def __init__(self, name: str): 36 self.name = name 37 self.failing_average = 0.0 38 self.samples = 0 39 40 def update(self, failed: bool): 41 """Update with fail of pass.""" 42 self.failing_average = (failed + self.samples * self.failing_average) / (self.samples + 1) 43 self.samples += 1 44 45 def failed(self) -> bool: 46 """Log failure.""" 47 logger.write_warning_to_report(f"{self.name} test failed for {self.failing_average:%} of test.") 48 return self.failing_average != 0.0 49 50 51current_log = FailureLog("Current") 52soc_log = FailureLog("SOC") 53cycle_log = FailureLog("Cycle") 54 55 56def log_data(start_time: float, serial_data: dict[str, int | bool | str]): 57 """Log data while charging/discharging.""" 58 elapsed_time = time.perf_counter() - start_time 59 _bms.csv.cycle_smbus.record(elapsed_time, suppress_smbus=True) 60 voltages = [f"{cell.measured_volts:.2f}V" for cell in _bms.cells.values()] 61 cell_socs = [cell.state_of_charge for cell in _bms.cells.values()] 62 cell_socs_str = ", ".join(map(lambda soc: f"{soc:%}", cell_socs)) 63 serial_amps = float(serial_data["mamps"]) / 1000 64 serial_soc = float(serial_data["percent_charged"]) / 100 65 logger.write_info_to_report( 66 f"Elapsed Time: {elapsed_time}, Cell Voltages: {', '.join(voltages)}, " 67 f"Cell SOCs: {cell_socs_str}, Serial SOC: {serial_soc}, Serial Amps: {serial_amps}" 68 ) 69 70 # Check Current 71 amps_error = abs(EXPECTED_CURRENT - serial_amps) 72 if failed := amps_error > 0.100: 73 logger.write_warning_to_report( 74 f"Current error of {amps_error * 1000} mA is more than 100 mA. " 75 f"Current was {serial_amps} A (expected {EXPECTED_CURRENT} A)" 76 ) 77 current_log.update(failed) 78 79 # Check SOC 80 soc_error = max(abs(cell_soc - serial_soc) for cell_soc in cell_socs) 81 if failed := soc_error > 0.05: 82 logger.write_warning_to_report( 83 f"SOC error of {soc_error:%} is more than 5%. SOC was {serial_soc:%} (expected {cell_socs_str})" 84 ) 85 soc_log.update(failed) 86 87 time.sleep(SAMPLE_TIME) 88 89 90@contextmanager 91def managed_charger_relay(): 92 """Context manager for charger relay.""" 93 _plateset.charger_switch = True 94 logger.write_info_to_report("Charge relay enabled, waiting 2 minutes...") 95 time.sleep(120) 96 try: 97 yield 98 finally: 99 _plateset.charger_switch = False 100 logger.write_info_to_report("Charge relay disabled") 101 102 103@pytest.mark.sim_cells 104def test_smart_charger(): 105 """ 106 | Requirement | Smart Charger Cycles | 107 | :------------------- | :--------------------------------------------------------------------------------------- | 108 | GitHub Issue(s) | turnaroundfactor/HITL#467 | 109 | Instructions | 1. Charge relays on until fully charged (0A) </br>\ 110 2. Discharge relays on until fully discharged at 10V (electronic load) </br>\ 111 3. Repeat 10 times total | 112 | Pass / Fail Criteria | Charge cycle increments, SOC matches cell sims, charge current is 1-2A (depends on test) | 113 """ 114 115 logger.write_info_to_report("Sleeping 10 seconds...") 116 time.sleep(10) 117 118 start_time = time.perf_counter() 119 _i2c_file_sniffer.start(start_time) # Begin scanning i2c to csv 120 serial_data = serial_monitor.read(latest=True) 121 starting_cycles = serial_data["charge_cycles"] 122 for cycle in range(1, 11): 123 # Charge 124 with managed_charger_relay(): 125 while serial_data["mamps"] > 100: 126 log_data(start_time, serial_data) 127 serial_data = serial_monitor.read(latest=True) 128 129 # Discharge 130 with _bms.load(2.0): 131 while serial_data["mvolt_terminal"] > 10_000: 132 log_data(start_time, serial_data) 133 serial_data = serial_monitor.read(latest=True) 134 135 # Check Charge Cycles 136 if failed := serial_data["charge_cycles"] != starting_cycles + cycle: 137 logger.write_warning_to_report( 138 f"Charge cycle was {serial_data['charge_cycles']} (expected {starting_cycles + cycle})" 139 ) 140 cycle_log.update(failed) 141 142 if current_log.failed() or soc_log.failed() or cycle_log.failed(): 143 pytest.fail("Failed")
SAMPLE_TIME =
10
EXPECTED_CURRENT =
2.0
FAILED =
{'current': [0, 0], 'soc': [0, 0], 'cycle': [0, 0]}
class
FailureLog:
33class FailureLog: 34 """Monitor how successful a test is from 0 (pass) to 1 (fail).""" 35 36 def __init__(self, name: str): 37 self.name = name 38 self.failing_average = 0.0 39 self.samples = 0 40 41 def update(self, failed: bool): 42 """Update with fail of pass.""" 43 self.failing_average = (failed + self.samples * self.failing_average) / (self.samples + 1) 44 self.samples += 1 45 46 def failed(self) -> bool: 47 """Log failure.""" 48 logger.write_warning_to_report(f"{self.name} test failed for {self.failing_average:%} of test.") 49 return self.failing_average != 0.0
Monitor how successful a test is from 0 (pass) to 1 (fail).
current_log =
<FailureLog object>
soc_log =
<FailureLog object>
cycle_log =
<FailureLog object>
def
log_data(start_time: float, serial_data: dict[str, int | bool | str]):
57def log_data(start_time: float, serial_data: dict[str, int | bool | str]): 58 """Log data while charging/discharging.""" 59 elapsed_time = time.perf_counter() - start_time 60 _bms.csv.cycle_smbus.record(elapsed_time, suppress_smbus=True) 61 voltages = [f"{cell.measured_volts:.2f}V" for cell in _bms.cells.values()] 62 cell_socs = [cell.state_of_charge for cell in _bms.cells.values()] 63 cell_socs_str = ", ".join(map(lambda soc: f"{soc:%}", cell_socs)) 64 serial_amps = float(serial_data["mamps"]) / 1000 65 serial_soc = float(serial_data["percent_charged"]) / 100 66 logger.write_info_to_report( 67 f"Elapsed Time: {elapsed_time}, Cell Voltages: {', '.join(voltages)}, " 68 f"Cell SOCs: {cell_socs_str}, Serial SOC: {serial_soc}, Serial Amps: {serial_amps}" 69 ) 70 71 # Check Current 72 amps_error = abs(EXPECTED_CURRENT - serial_amps) 73 if failed := amps_error > 0.100: 74 logger.write_warning_to_report( 75 f"Current error of {amps_error * 1000} mA is more than 100 mA. " 76 f"Current was {serial_amps} A (expected {EXPECTED_CURRENT} A)" 77 ) 78 current_log.update(failed) 79 80 # Check SOC 81 soc_error = max(abs(cell_soc - serial_soc) for cell_soc in cell_socs) 82 if failed := soc_error > 0.05: 83 logger.write_warning_to_report( 84 f"SOC error of {soc_error:%} is more than 5%. SOC was {serial_soc:%} (expected {cell_socs_str})" 85 ) 86 soc_log.update(failed) 87 88 time.sleep(SAMPLE_TIME)
Log data while charging/discharging.
@contextmanager
def
managed_charger_relay():
91@contextmanager 92def managed_charger_relay(): 93 """Context manager for charger relay.""" 94 _plateset.charger_switch = True 95 logger.write_info_to_report("Charge relay enabled, waiting 2 minutes...") 96 time.sleep(120) 97 try: 98 yield 99 finally: 100 _plateset.charger_switch = False 101 logger.write_info_to_report("Charge relay disabled")
Context manager for charger relay.
@pytest.mark.sim_cells
def
test_smart_charger():
104@pytest.mark.sim_cells 105def test_smart_charger(): 106 """ 107 | Requirement | Smart Charger Cycles | 108 | :------------------- | :--------------------------------------------------------------------------------------- | 109 | GitHub Issue(s) | turnaroundfactor/HITL#467 | 110 | Instructions | 1. Charge relays on until fully charged (0A) </br>\ 111 2. Discharge relays on until fully discharged at 10V (electronic load) </br>\ 112 3. Repeat 10 times total | 113 | Pass / Fail Criteria | Charge cycle increments, SOC matches cell sims, charge current is 1-2A (depends on test) | 114 """ 115 116 logger.write_info_to_report("Sleeping 10 seconds...") 117 time.sleep(10) 118 119 start_time = time.perf_counter() 120 _i2c_file_sniffer.start(start_time) # Begin scanning i2c to csv 121 serial_data = serial_monitor.read(latest=True) 122 starting_cycles = serial_data["charge_cycles"] 123 for cycle in range(1, 11): 124 # Charge 125 with managed_charger_relay(): 126 while serial_data["mamps"] > 100: 127 log_data(start_time, serial_data) 128 serial_data = serial_monitor.read(latest=True) 129 130 # Discharge 131 with _bms.load(2.0): 132 while serial_data["mvolt_terminal"] > 10_000: 133 log_data(start_time, serial_data) 134 serial_data = serial_monitor.read(latest=True) 135 136 # Check Charge Cycles 137 if failed := serial_data["charge_cycles"] != starting_cycles + cycle: 138 logger.write_warning_to_report( 139 f"Charge cycle was {serial_data['charge_cycles']} (expected {starting_cycles + cycle})" 140 ) 141 cycle_log.update(failed) 142 143 if current_log.failed() or soc_log.failed() or cycle_log.failed(): 144 pytest.fail("Failed")
| Requirement | Smart Charger Cycles |
|---|---|
| GitHub Issue(s) | turnaroundfactor/HITL#467 |
| Instructions | 1. Charge relays on until fully charged (0A) 2. Discharge relays on until fully discharged at 10V (electronic load) 3. Repeat 10 times total |
| Pass / Fail Criteria | Charge cycle increments, SOC matches cell sims, charge current is 1-2A (depends on test) |