hitl_tester.test_cases.bms.test_q_max

See: https://github.com/turnaroundfactor/battery-benchtop-rev1/issues/244

Fix issues with q_max accuracy being off.

The full test plan is below.

  1. Charge the pack completely, let it rest overnight.
  2. Discharge it and pause at all OCV intervals (95, 90, 80, 70...)
  3. During the pause set discharge current to 0 for 5-10 seconds
  4. Measure the voltage during this time
  5. Take notice of the voltage difference between when its charged and discharged

Used in these test plans:

  • 2590_qmax_b ⠀⠀⠀(bms/2590_qmax_b.plan)

Example Command (warning: test plan may run other test cases):

  • ./hitl_tester.py 2590_qmax_b -DMINUTE=60 -DHOUR=3600 -DMAX_CHARGE_TIME=43200 -DCHARGE_VOLTAGE=16.8 -DCHARGE_OV_PROTECTION=16.85 -DCHARGE_CURRENT_A=3 -DCHARGE_TERMINATION_CURRENT=0.1 -DCHARGE_SAMPLE_INTERVAL=1 -DMIN_READINGS=3 -DMAX_DISCHARGE_TIME=43200 -DMAX_DISCHARGE_STEP_TIME=3600 -DDISCHARGE_CURRENT_A=2 -DDISCHARGE_VOLTAGE=10 -DDISCHARGE_UV_PROTECTION=9.95 -DDISCHARGE_SAMPLE_INTERVAL=1 -DRESTING_TIME=30 -DRESTING_SAMPLE_INTERVAL=1 -DCAPACITY=11.485347
  1"""
  2See: https://github.com/turnaroundfactor/battery-benchtop-rev1/issues/244
  3
  4Fix issues with q_max accuracy being off.
  5
  6The full test plan is below.
  7
  81. Charge the pack completely, let it rest overnight.
  92. Discharge it and pause at all OCV intervals (95, 90, 80, 70...)
 103. During the pause set discharge current to 0 for 5-10 seconds
 114. Measure the voltage during this time
 125. Take notice of the voltage difference between when its charged and discharged
 13"""
 14
 15import pytest
 16
 17from hitl_tester.modules.bms.bms_hw import BMSHardware, UserInterrupt
 18from hitl_tester.modules.bms.plateset import Plateset
 19from hitl_tester.modules.bms_types import DischargeType
 20
 21# Global Variables that can be shared between tests
 22MINUTE = 60
 23HOUR = 60 * MINUTE
 24
 25MAX_CHARGE_TIME = 12 * HOUR
 26CHARGE_VOLTAGE = 16.8
 27CHARGE_OV_PROTECTION = 16.85
 28CHARGE_CURRENT_A = 3
 29CHARGE_TERMINATION_CURRENT = 0.1
 30CHARGE_SAMPLE_INTERVAL = 1
 31MIN_READINGS = 3  # How many readings to take before passing (for erroneous readings).
 32
 33MAX_DISCHARGE_TIME = 12 * HOUR
 34MAX_DISCHARGE_STEP_TIME = HOUR
 35DISCHARGE_CURRENT_A = 2
 36DISCHARGE_VOLTAGE = 10
 37DISCHARGE_UV_PROTECTION = 9.95
 38DISCHARGE_SAMPLE_INTERVAL = 1
 39
 40RESTING_TIME = 30
 41RESTING_SAMPLE_INTERVAL = 1
 42
 43CAPACITY = 11.485347  # Set by the discharge cycle (value from last Q max test)
 44
 45bms_hardware = BMSHardware(pytest.flags)  # type: ignore[arg-type]
 46bms_hardware.init()
 47plateset = Plateset()
 48
 49bms_hardware.csv.cycle = bms_hardware.csv.cycle_smbus
 50bms_hardware.remaining_capacity_percentage = 100
 51
 52
 53def standard_step_discharge(
 54    percent_discharge: float,
 55    total_ah: float,
 56    max_time: float = MAX_DISCHARGE_STEP_TIME,
 57    discharge_i: float = DISCHARGE_CURRENT_A,
 58    discharge_sample_interval: float = DISCHARGE_SAMPLE_INTERVAL,
 59    uv_protection: float = DISCHARGE_UV_PROTECTION,
 60):
 61    """Run a step discharge cycle."""
 62    bms_hardware.max_time = max_time
 63    bms_hardware.current = discharge_i
 64    bms_hardware.sample_interval = discharge_sample_interval
 65    bms_hardware.uv_protection = uv_protection
 66    bms_hardware.percent_discharge = percent_discharge
 67    bms_hardware.total_ah = total_ah
 68
 69    bms_hardware.run_discharge_step_cycle()
 70
 71
 72def standard_rest(max_time: float = RESTING_TIME, sample_rate: float = RESTING_SAMPLE_INTERVAL):
 73    """Run a resting cycle."""
 74    bms_hardware.max_time = max_time
 75    bms_hardware.sample_interval = sample_rate
 76    bms_hardware.run_resting_cycle()
 77
 78
 79def test_full_charge_cycle_1():
 80    """Completely charge cells."""
 81    bms_hardware.voltage = CHARGE_VOLTAGE
 82    bms_hardware.ov_protection = CHARGE_OV_PROTECTION
 83    bms_hardware.current = CHARGE_CURRENT_A
 84    bms_hardware.termination_current = CHARGE_TERMINATION_CURRENT
 85    bms_hardware.max_time = MAX_CHARGE_TIME
 86    bms_hardware.sample_interval = CHARGE_SAMPLE_INTERVAL
 87    bms_hardware.minimum_readings = MIN_READINGS
 88
 89    plateset.ce_switch = True
 90    bms_hardware.run_li_charge_cycle()
 91    plateset.ce_switch = False
 92
 93
 94def test_full_discharge_cycle():
 95    """Completely discharge cells. Required to get capacity."""
 96    global CAPACITY
 97
 98    bms_hardware.max_time = MAX_DISCHARGE_TIME
 99    bms_hardware.sample_interval = DISCHARGE_SAMPLE_INTERVAL
100    bms_hardware.discharge_type = DischargeType.CONSTANT_CURRENT
101    bms_hardware.current = DISCHARGE_CURRENT_A
102    bms_hardware.uv_protection = DISCHARGE_UV_PROTECTION
103    bms_hardware.voltage = DISCHARGE_VOLTAGE
104
105    CAPACITY = bms_hardware.run_discharge_cycle()
106    print(f"Ah = {CAPACITY}")
107    assert CAPACITY != 0  # make sure we didn't get an error
108
109
110def test_full_charge_cycle_2():
111    """Completely charge cells."""
112    bms_hardware.voltage = CHARGE_VOLTAGE
113    bms_hardware.ov_protection = CHARGE_OV_PROTECTION
114    bms_hardware.current = CHARGE_CURRENT_A
115    bms_hardware.termination_current = CHARGE_TERMINATION_CURRENT
116    bms_hardware.max_time = MAX_CHARGE_TIME
117    bms_hardware.sample_interval = CHARGE_SAMPLE_INTERVAL
118    bms_hardware.minimum_readings = MIN_READINGS
119
120    plateset.ce_switch = True
121    bms_hardware.run_li_charge_cycle()
122    plateset.ce_switch = False
123    UserInterrupt.force_pause()  # Suspend the test
124
125
126def test_step_discharge():
127    """Record the voltage at each state of charge."""
128    soc = 100
129    ocv_steps = (100, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 7.5, 5, 2.5, 0)
130    for new_soc in ocv_steps:
131        if new_soc != ocv_steps[0]:
132            if new_soc == ocv_steps[-1]:
133                bms_hardware.discharge_until_undervoltage = True
134            standard_step_discharge((soc - new_soc) / 100, CAPACITY)
135        standard_rest()
136        soc = new_soc
MINUTE = 60
HOUR = 3600
MAX_CHARGE_TIME = 43200
CHARGE_VOLTAGE = 16.8
CHARGE_OV_PROTECTION = 16.85
CHARGE_CURRENT_A = 3
CHARGE_TERMINATION_CURRENT = 0.1
CHARGE_SAMPLE_INTERVAL = 1
MIN_READINGS = 3
MAX_DISCHARGE_TIME = 43200
MAX_DISCHARGE_STEP_TIME = 3600
DISCHARGE_CURRENT_A = 2
DISCHARGE_VOLTAGE = 10
DISCHARGE_UV_PROTECTION = 9.95
DISCHARGE_SAMPLE_INTERVAL = 1
RESTING_TIME = 30
RESTING_SAMPLE_INTERVAL = 1
CAPACITY = 11.485347
def standard_step_discharge( percent_discharge: float, total_ah: float, max_time: float = 3600, discharge_i: float = 2, discharge_sample_interval: float = 1, uv_protection: float = 9.95):
54def standard_step_discharge(
55    percent_discharge: float,
56    total_ah: float,
57    max_time: float = MAX_DISCHARGE_STEP_TIME,
58    discharge_i: float = DISCHARGE_CURRENT_A,
59    discharge_sample_interval: float = DISCHARGE_SAMPLE_INTERVAL,
60    uv_protection: float = DISCHARGE_UV_PROTECTION,
61):
62    """Run a step discharge cycle."""
63    bms_hardware.max_time = max_time
64    bms_hardware.current = discharge_i
65    bms_hardware.sample_interval = discharge_sample_interval
66    bms_hardware.uv_protection = uv_protection
67    bms_hardware.percent_discharge = percent_discharge
68    bms_hardware.total_ah = total_ah
69
70    bms_hardware.run_discharge_step_cycle()

Run a step discharge cycle.

def standard_rest(max_time: float = 30, sample_rate: float = 1):
73def standard_rest(max_time: float = RESTING_TIME, sample_rate: float = RESTING_SAMPLE_INTERVAL):
74    """Run a resting cycle."""
75    bms_hardware.max_time = max_time
76    bms_hardware.sample_interval = sample_rate
77    bms_hardware.run_resting_cycle()

Run a resting cycle.

def test_full_charge_cycle_1():
80def test_full_charge_cycle_1():
81    """Completely charge cells."""
82    bms_hardware.voltage = CHARGE_VOLTAGE
83    bms_hardware.ov_protection = CHARGE_OV_PROTECTION
84    bms_hardware.current = CHARGE_CURRENT_A
85    bms_hardware.termination_current = CHARGE_TERMINATION_CURRENT
86    bms_hardware.max_time = MAX_CHARGE_TIME
87    bms_hardware.sample_interval = CHARGE_SAMPLE_INTERVAL
88    bms_hardware.minimum_readings = MIN_READINGS
89
90    plateset.ce_switch = True
91    bms_hardware.run_li_charge_cycle()
92    plateset.ce_switch = False

Completely charge cells.

def test_full_discharge_cycle():
 95def test_full_discharge_cycle():
 96    """Completely discharge cells. Required to get capacity."""
 97    global CAPACITY
 98
 99    bms_hardware.max_time = MAX_DISCHARGE_TIME
100    bms_hardware.sample_interval = DISCHARGE_SAMPLE_INTERVAL
101    bms_hardware.discharge_type = DischargeType.CONSTANT_CURRENT
102    bms_hardware.current = DISCHARGE_CURRENT_A
103    bms_hardware.uv_protection = DISCHARGE_UV_PROTECTION
104    bms_hardware.voltage = DISCHARGE_VOLTAGE
105
106    CAPACITY = bms_hardware.run_discharge_cycle()
107    print(f"Ah = {CAPACITY}")
108    assert CAPACITY != 0  # make sure we didn't get an error

Completely discharge cells. Required to get capacity.

def test_full_charge_cycle_2():
111def test_full_charge_cycle_2():
112    """Completely charge cells."""
113    bms_hardware.voltage = CHARGE_VOLTAGE
114    bms_hardware.ov_protection = CHARGE_OV_PROTECTION
115    bms_hardware.current = CHARGE_CURRENT_A
116    bms_hardware.termination_current = CHARGE_TERMINATION_CURRENT
117    bms_hardware.max_time = MAX_CHARGE_TIME
118    bms_hardware.sample_interval = CHARGE_SAMPLE_INTERVAL
119    bms_hardware.minimum_readings = MIN_READINGS
120
121    plateset.ce_switch = True
122    bms_hardware.run_li_charge_cycle()
123    plateset.ce_switch = False
124    UserInterrupt.force_pause()  # Suspend the test

Completely charge cells.

def test_step_discharge():
127def test_step_discharge():
128    """Record the voltage at each state of charge."""
129    soc = 100
130    ocv_steps = (100, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 7.5, 5, 2.5, 0)
131    for new_soc in ocv_steps:
132        if new_soc != ocv_steps[0]:
133            if new_soc == ocv_steps[-1]:
134                bms_hardware.discharge_until_undervoltage = True
135            standard_step_discharge((soc - new_soc) / 100, CAPACITY)
136        standard_rest()
137        soc = new_soc

Record the voltage at each state of charge.