hitl_tester.test_cases.bms.test_2590_faults

Testing requirements on BB2590_v2 before live cells are used.

All of these faults should be read out through the flags. We should be able to read the runtime flags and ensure each bit corresponds to the proper pre fault, fault, and permanent fault.

Used in these test plans:

  • 2590_assembly_b ⠀⠀⠀(bms/2590_assembly_b.plan)
  • bb2590_a_firmware ⠀⠀⠀(bms/bb2590_a_firmware.plan)
  • bb2590_b_firmware ⠀⠀⠀(bms/bb2590_b_firmware.plan)

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

  • ./hitl_tester.py 2590_assembly_b -DCELL_VOLTAGE=3.8002
  1"""
  2Testing requirements on BB2590_v2 before live cells are used.
  3
  4All of these faults should be read out through the flags. We should be able to read the runtime flags and ensure each
  5bit corresponds to the proper pre fault, fault, and permanent fault.
  6"""
  7
  8from __future__ import annotations
  9
 10import time
 11
 12import pytest
 13
 14from hitl_tester.modules.bms import smbus_types
 15from hitl_tester.modules.bms.bms_hw import BMSHardware
 16from hitl_tester.modules.bms.cell import Cell
 17from hitl_tester.modules.bms.event_watcher import SerialWatcher
 18from hitl_tester.modules.bms.plateset import Plateset
 19from hitl_tester.modules.bms.smbus import SMBus
 20from hitl_tester.modules.bms.smbus_types import SMBusReg
 21from hitl_tester.modules.logger import logger
 22
 23CELL_VOLTAGE = 3.8002
 24
 25bms_hardware = BMSHardware(pytest.flags)  # type: ignore[arg-type]
 26bms_hardware.init()
 27
 28serial_watcher = SerialWatcher()
 29plateset = Plateset()
 30smbus = SMBus()
 31
 32
 33def test_reset_cell_sims():
 34    """Activate cell sims and set appropriate temperatures."""
 35    logger.write_info_to_report("Powering down cell sims")
 36    for cell in bms_hardware.cells.values():
 37        cell.disengage_safety_protocols = True
 38        cell.volts = 0.0001
 39    time.sleep(5)
 40    logger.write_info_to_report("Powering up cell sims")
 41    for cell in bms_hardware.cells.values():
 42        cell.volts = CELL_VOLTAGE
 43        cell.disengage_safety_protocols = False
 44    for cell in bms_hardware.cells.values():
 45        cell.exact_volts = CELL_VOLTAGE
 46    logger.write_info_to_report("Setting temperature to 15°C")
 47    plateset.thermistor1 = 15
 48    plateset.thermistor2 = 15
 49    logger.write_info_to_report("Enabling faults")
 50    time.sleep(1)
 51    smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, smbus_types.BMSCommands.FAULT_ENABLE)  # Enable faults
 52
 53
 54def test_wakeup_1():
 55    """
 56    The BMS should be in a state of slumber if the current measured is between -46mA and 19ma. This is done using
 57    internal comparators on the board to a logical AND chip feeding into an interrupt pin. To test this, 3 different
 58    currents should be set. One current below -46ma, another current between -46mA and 19ma, and another current above
 59    19ma. If the current is within the allowable range, we should read logic 1 on the N_WAKEUP pin. If the current is
 60    outside (above or below) we should read logic 0 on the pin.
 61
 62    # Test
 63    - Default state at 0 mA (between -46mA and 19ma)
 64      - n_wakeup_gpio = 1
 65    - Discharging at 100 mA (below -46 mA)
 66      - n_wakeup_gpio = 0
 67    - Discharging at 10 mA (between -46mA and 19ma)
 68      - n_wakeup_gpio = 1
 69    - Charging at 100 mA (above 19 mA)
 70      - n_wakeup_gpio = 0
 71    """
 72    logger.write_info_to_report("Testing Wakeup")
 73    test_reset_cell_sims()
 74    serial_watcher.start()
 75
 76    logger.write_info_to_report("Default state")
 77    serial_watcher.assert_true("n_wakeup_gpio", True, 1)
 78
 79    logger.write_info_to_report("Discharging 100 mA")
 80    bms_hardware.load.mode_cc()
 81    bms_hardware.load.amps = 0.100
 82    bms_hardware.load.enable()
 83    serial_watcher.assert_true("n_wakeup_gpio", False, 2)
 84
 85    logger.write_info_to_report("Discharging 10 mA")
 86    bms_hardware.load.amps = 0.010
 87    serial_watcher.assert_true("n_wakeup_gpio", True, 3)
 88    bms_hardware.load.disable()
 89
 90    logger.write_info_to_report("Charging 100 mA")
 91    bms_hardware.charger.set_profile(16.8, 0.100)
 92    plateset.ce_switch = True
 93    bms_hardware.charger.enable()
 94    serial_watcher.assert_true("n_wakeup_gpio", False, 4)
 95    bms_hardware.charger.disable()
 96    plateset.ce_switch = False
 97
 98    serial_watcher.stop()
 99
100
101def test_oc_oneshot():
102    """
103    OC Oneshot: This is an interrupt that will trigger if we sink a ton of current into the battery, this cannot be
104    tested with just software and the comparators used for this have not been tested for hardware yet.
105
106    The bms should have an interrupt be fired when the current reaches the max amount (I think ~63 amps). this will be
107    a test for Tyler to do on his own
108
109    NOTE: High current. Only run this on a modified board.
110    """
111
112
113def test_overtemp_faults():
114    """
115    If our batteries get too hot, we must trigger a fault. This is common during high discharge or charging cycles.
116    There are 3 different environments where we would trigger an overtemp fault: charge, discharge and resting. While
117    charging, if we are above 45C we must trigger a fault If we are resting or discharging at 59 degrees we must
118    trigger a fault. Both of these faults should trigger a prefault condition in our flags. After we cycle again we
119    should then trigger a full fault. If the temperature ever goes above 93 degrees, the fault should never clear and
120    we should be in permanent fault and trigger the fets. (This should also be seen in the flags)
121
122    # Test Overview (reset inbetween tests)
123    - Default state at 15°C
124      - prefault_overtemp_discharge = 0
125      - fault_overtemp_discharge = 0
126      - prefault_overtemp_charge = 0
127      - fault_overtemp_charge = 0
128      - permanentdisable_overtemp = 0
129      - measure_output_fets_disabled = 0
130
131    - Resting at 59°C (at or above 59°C)
132      - prefault_overtemp_discharge = 1
133      - fault_overtemp_discharge = 1
134
135    - Discharging at 100 mA, 59°C (at or above 59°C)
136      - prefault_overtemp_discharge = 1
137      - fault_overtemp_discharge = 1
138
139    - Charging at 100 mA, 46°C (at or above 45°C)
140      - prefault_overtemp_charge = 1
141      - fault_overtemp_charge = 1permanentdisable_overtemp
142
143    - Charging at 100 mA, 94°C (at or above 93°C)
144      - permanentdisable_overtemp = 1
145
146    - Discharging at 100 mA, 94°C (at or above 93°C)
147      - permanentdisable_overtemp = 1
148
149    - Resting at 94°C (at or above 93°C)
150      - permanentdisable_overtemp = 1
151    """
152    logger.write_info_to_report("Testing Overtemp")
153    test_reset_cell_sims()
154    serial_watcher.start()
155
156    # Test default state
157    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
158    plateset.thermistor1 = 15
159    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 1)
160    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 1)
161    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1)
162    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1)
163    serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1)
164    serial_watcher.assert_true("flags.measure_output_fets_disabled", False, 1)
165
166    # Test resting overtemp
167    logger.write_info_to_report("Resting at 59°C")
168    plateset.thermistor1 = 59
169    serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 2)
170    serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 2)
171    logger.write_info_to_report("Resting at 15°C")
172    plateset.thermistor1 = 15
173    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 3)
174    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 3)
175
176    # Test discharging overtemp
177    logger.write_info_to_report("Discharging at -100 mA, 59°C")
178    bms_hardware.load.mode_cc()
179    bms_hardware.load.amps = 0.100
180    bms_hardware.load.enable()
181    plateset.thermistor1 = 59
182    serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 4)
183    serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 4)
184    logger.write_info_to_report("Discharging at -100 mA, 15°C")
185    plateset.thermistor1 = 15
186    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 5)
187    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 5)
188    bms_hardware.load.disable()
189
190    # Test charging overtemp
191    logger.write_info_to_report("Charging at 100 mA, 65°C")
192    bms_hardware.charger.set_profile(16.8, 0.1)
193    plateset.ce_switch = True
194    bms_hardware.charger.enable()
195    plateset.thermistor1 = 65
196    serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2)
197    serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2)
198    logger.write_info_to_report("Charging at 100 mA, 45°C")
199    plateset.thermistor1 = 45
200    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3)
201    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3)
202    bms_hardware.charger.disable()
203    plateset.ce_switch = False
204
205    # Test charging permanent disable
206    logger.write_info_to_report("Charging at 100 mA, 94°C")
207    plateset.disengage_safety_protocols = True
208    plateset.thermistor1 = 94
209    plateset.disengage_safety_protocols = False
210    bms_hardware.charger.set_profile(16.8, 0.1)
211    plateset.ce_switch = True
212    bms_hardware.charger.enable()
213
214    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2)
215
216    logger.write_info_to_report("Charging at 100 mA, 15°C")
217    plateset.thermistor1 = 15
218    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3)
219
220    bms_hardware.charger.disable()
221    plateset.ce_switch = False
222    test_reset_cell_sims()
223
224    # Test discharging permanent disable
225    logger.write_info_to_report("Discharging at -100 mA, 94°C")
226    plateset.disengage_safety_protocols = True
227    plateset.thermistor1 = 94
228    plateset.disengage_safety_protocols = False
229    bms_hardware.load.mode_cc()
230    bms_hardware.load.amps = 0.100
231    bms_hardware.load.enable()
232
233    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 4)
234
235    logger.write_info_to_report("Discharging at -100 mA, 15°C")
236    plateset.thermistor1 = 15
237    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 5)
238
239    bms_hardware.load.disable()
240    test_reset_cell_sims()
241
242    # Test resting permanent disable
243    plateset.disengage_safety_protocols = True
244    logger.write_info_to_report("Resting at 94°C")
245    plateset.thermistor1 = 94
246    plateset.disengage_safety_protocols = False
247    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 6)
248
249    logger.write_info_to_report("Resting at 15°C")
250    plateset.thermistor1 = 15
251    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 7)
252
253    serial_watcher.stop()
254
255
256def test_undertemp_faults():
257    """
258    This occurs when we read more than 20 mamps from the battery, if any of the cells are under 0 degrees Celsius this
259    will trigger a fault. This will be cleared if we go above -2C. Regardless of current being measured, if we ever
260    read below -20C, this should trigger a fault. This fault should not be cleared until we are above -18C
261
262    *** THIS FAULT NEEDS TO BE INVESTIGATED
263    https://github.com/orgs/turnaroundfactor/projects/24?pane=issue&itemId=50150194
264    """
265    logger.write_info_to_report("Testing Undertemp")
266    test_reset_cell_sims()
267    serial_watcher.start()
268
269    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
270    plateset.thermistor1 = 15
271
272    # Discharging flags
273    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1)
274    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1)
275
276    # Charging flags
277    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1)
278    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1)
279
280    logger.write_info_to_report("Resting at -21°C")
281    plateset.thermistor1 = -21
282    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2)
283    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2)
284    logger.write_info_to_report("Resting at -17°C")
285    plateset.thermistor1 = -17
286    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3)
287    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3)
288
289    logger.write_info_to_report("Discharging at -100 mA, -21°C")
290    bms_hardware.load.mode_cc()
291    bms_hardware.load.amps = 0.100
292    bms_hardware.load.enable()
293    plateset.thermistor1 = -21
294    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 4)
295    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 4)
296    logger.write_info_to_report("Discharging at -100 mA, -17°C")
297    plateset.thermistor1 = -17
298    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 5)
299    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 5)
300    bms_hardware.load.disable()
301
302    logger.write_info_to_report("Charging at 100 mA, -25°C")
303    bms_hardware.charger.set_profile(16.8, 0.1)
304    plateset.ce_switch = True
305    bms_hardware.charger.enable()
306    plateset.thermistor1 = -25
307    serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2)
308    serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2)
309    logger.write_info_to_report("Charging at 100 mA, -15°C")
310    plateset.thermistor1 = -15
311    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3)
312    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
313    bms_hardware.charger.disable()
314    plateset.ce_switch = False
315
316    serial_watcher.stop()
317
318
319def set_exact_volts(cell: Cell, voltage: float, compensation: float = 0.08):
320    """What the BMS reads won't exactly match the set voltage, thus we need slight adjustments."""
321    cell.exact_volts = voltage + compensation
322    logger.write_debug_to_report(f"Cell is {cell.volts}V")
323
324
325def test_overvoltage_faults():
326    """
327    While charging, we need to monitor the voltage of our cells. Specifically, if a cell ever goes above 4.205 Volts,
328    we should trigger a prefault. If this prefault exsists for more than 3 seconds, we then should trigger a full
329    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
330    should be able to clear the fault
331    """
332    logger.write_info_to_report("Testing Overvoltage")
333    test_reset_cell_sims()
334    test_cell = bms_hardware.cells[1]
335    serial_watcher.start()
336    test_cell.disengage_safety_protocols = True
337
338    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
339    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1)
340    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1)
341    serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1)
342
343    bms_hardware.charger.set_profile(16.8, 0.1)
344    plateset.ce_switch = True
345    bms_hardware.charger.enable()
346    logger.write_info_to_report("Charging at 100 mA, 4.21 V")
347    set_exact_volts(test_cell, 4.21)
348    serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2)
349    serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2)
350
351    logger.write_info_to_report("Charging at 100 mA, 4.10 V")
352    set_exact_volts(test_cell, 4.10)
353    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3)
354    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3)
355
356    logger.write_info_to_report("Charging at 100 mA, 4.26 V")
357    set_exact_volts(test_cell, 4.26)
358    serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2)
359
360    logger.write_info_to_report("Charging at 100 mA, 4.10 V")
361    set_exact_volts(test_cell, 4.10)
362    serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3)
363
364    bms_hardware.charger.disable()
365    plateset.ce_switch = False
366
367    serial_watcher.stop()
368
369
370def test_undervoltage_faults():
371    """
372    This has also been validated in software, meaning the logic should properly handle a situation with a cell
373    discharging too low, however this has not yet been tested in hardware with a cell sensor reading that low of a
374    voltage and triggering a fault.
375
376    If we are reading less than 20mamps from the cells, we should be able to trigger an under-voltage fault. If we
377    read less than 2.4 volts, we must trigger a fault. If this fault persists for over 1 second, we should then trigger
378    a full fault. We will not clear this fault unless we are able to read above 2.5 volts.
379    If we are reading over 400mamps and a cell reads less than 2.325 volts, we must trigger a cell voltage charge min
380    prefault, if this persists for another bms software cycle we will trigger a full fault. This fault will clear when
381    we read above this voltage. If the cell voltage ever goes under 2.3 while charging, we must trigger a permanent
382    fault.
383    """
384    logger.write_info_to_report("Testing Undervoltage")
385    test_reset_cell_sims()
386    test_reset_cell_sims()
387    test_cell = bms_hardware.cells[1]
388    for cell in bms_hardware.cells.values():
389        cell.disengage_safety_protocols = True
390        set_exact_volts(cell, 3.0, 0.05)  # Must be low enough to not trigger cell imbalance [abs(high - low) > 1.5V]
391    serial_watcher.start()
392
393    logger.write_info_to_report("Resting at 0 mA, 3.0 V")
394    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1)
395    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1)
396    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1)
397    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1)
398    serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1)
399
400    logger.write_info_to_report("Resting at 0 mA, 2.3 V")
401    set_exact_volts(test_cell, 2.3, 0.05)
402    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2, wait_time=600)
403    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2, wait_time=600)
404
405    logger.write_info_to_report("Resting at 0 mA, 2.6 V")
406    set_exact_volts(test_cell, 2.6, 0.05)
407    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3, wait_time=600)
408    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3, wait_time=600)
409
410    bms_hardware.charger.set_profile(16.8, 0.500)
411    plateset.ce_switch = True
412    bms_hardware.charger.enable()
413    logger.write_info_to_report("Charging at 500 mA, 2.3 V")
414    set_exact_volts(test_cell, 2.3, 0.01)
415    serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2, wait_time=600)
416    serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2, wait_time=600)
417
418    logger.write_info_to_report("Charging at 500 mA, 2.4 V")
419    set_exact_volts(test_cell, 2.4, 0.05)
420    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3)
421    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3)
422
423    logger.write_info_to_report("Charging at 500 mA, 2.2 V")
424    set_exact_volts(test_cell, 2.2, 0.05)
425    serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2)
426
427    logger.write_info_to_report("Charging at 500 mA, 2.6 V")
428    set_exact_volts(test_cell, 2.6, 0.05)
429    serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3)
430
431    bms_hardware.charger.disable()
432    plateset.ce_switch = False
433
434    serial_watcher.stop()
435
436
437def test_cell_imbalance():
438    """Occurs when the difference between the highest and lowest cell is 1.5V."""
439    logger.write_info_to_report("Testing Cell Imbalance")
440    test_reset_cell_sims()
441    test_cell = bms_hardware.cells[1]
442    serial_watcher.start()
443    test_cell.disengage_safety_protocols = True
444
445    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
446    serial_watcher.assert_true("flags.prefault_cellimbalance", False, 1)
447    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1)
448
449    logger.write_info_to_report("Resting at 0 mA, 2.0 V")
450    set_exact_volts(test_cell, 2.0)
451    serial_watcher.assert_true("flags.prefault_cellimbalance", True, 2)
452    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True, 2)
453
454    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
455    set_exact_volts(test_cell, CELL_VOLTAGE)
456    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", False, 3)
457
458    serial_watcher.stop()
459
460
461def test_overvoltage_overtemp_faults():
462    """A combo test where we have a high temperature and high voltage."""
463    logger.write_info_to_report("Testing Overvoltage with Overtemp")
464    test_reset_cell_sims()
465    test_cell = bms_hardware.cells[1]
466    serial_watcher.start()
467    test_cell.disengage_safety_protocols = True
468
469    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
470    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1)
471    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1)
472    serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1)
473
474    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1)
475    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1)
476    serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1)
477
478    bms_hardware.charger.set_profile(16.8, 0.1)
479    plateset.ce_switch = True
480    bms_hardware.charger.enable()
481    logger.write_info_to_report("Charging at 100 mA, 4.21 V, 65°C")
482    set_exact_volts(test_cell, 4.21)
483    plateset.thermistor1 = 65
484    serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2)
485    serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2)
486    serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2)
487    serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2)
488
489    logger.write_info_to_report("Charging at 100 mA, 4.10 V, 45°C")
490    set_exact_volts(test_cell, 4.10)
491    plateset.thermistor1 = 45
492    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3)
493    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3)
494    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3)
495    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3)
496
497    logger.write_info_to_report("Charging at 100 mA, 4.26 V, 94°C")
498    set_exact_volts(test_cell, 4.26)
499    plateset.disengage_safety_protocols = True
500    plateset.thermistor1 = 94
501    plateset.disengage_safety_protocols = False
502    serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2)
503    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2)
504
505    logger.write_info_to_report("Charging at 100 mA, 4.10 V, 15°C")
506    set_exact_volts(test_cell, 4.10)
507    plateset.thermistor1 = 15
508    serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3)
509    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3)
510
511    bms_hardware.charger.disable()
512    plateset.ce_switch = False
513
514    serial_watcher.stop()
515
516
517def test_undervoltage_undertemp_faults():
518    """A combo test where we have a low temperature and low voltage."""
519    logger.write_info_to_report("Testing Undervoltage with Undertemp")
520    test_reset_cell_sims()
521    test_cell = bms_hardware.cells[1]
522    for cell in bms_hardware.cells.values():
523        cell.disengage_safety_protocols = True
524        set_exact_volts(cell, 3.0)  # Must be low enough to not trigger cell imbalance [abs(high - low) > 1.5V]
525    serial_watcher.start()
526
527    logger.write_info_to_report("Resting at 0 mA, 3.0 V, 15°C")
528    plateset.thermistor1 = 15
529    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1)
530    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1)
531    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1)
532    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1)
533    serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1)
534
535    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1)
536    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1)
537    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1)
538    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1)
539
540    logger.write_info_to_report("Resting at 0 mA, 2.3 V, -21°C")
541    set_exact_volts(test_cell, 2.3)
542    plateset.thermistor1 = -21
543    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2)
544    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2)
545    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2)
546    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2)
547
548    logger.write_info_to_report("Resting at 0 mA, 2.6 V, -17°C")
549    set_exact_volts(test_cell, 2.6)
550    plateset.thermistor1 = -17
551    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3)
552    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3)
553    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3)
554    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3)
555
556    bms_hardware.charger.set_profile(16.8, 0.500)
557    plateset.ce_switch = True
558    bms_hardware.charger.enable()
559    logger.write_info_to_report("Charging at 500 mA, 2.3 V, -25°C")
560    set_exact_volts(test_cell, 2.3, 0.01)
561    plateset.thermistor1 = -25
562    serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2)
563    serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2)
564    serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2)
565    serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2)
566
567    logger.write_info_to_report("Charging at 500 mA, 2.4 V, -15°C")
568    set_exact_volts(test_cell, 2.4)
569    plateset.thermistor1 = -15
570    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3)
571    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3)
572    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3)
573    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
574
575    logger.write_info_to_report("Charging at 500 mA, 2.2 V, 15°C")
576    set_exact_volts(test_cell, 2.2)
577    plateset.thermistor1 = 15
578    serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2)
579
580    logger.write_info_to_report("Charging at 500 mA, 2.6 V, 15°C")
581    set_exact_volts(test_cell, 2.6)
582    plateset.thermistor1 = 15
583    serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3)
584
585    bms_hardware.charger.disable()
586    plateset.ce_switch = False
587
588    serial_watcher.stop()
589
590
591def test_cell_imbalance_charge():
592    """Occurs when the difference between the highest and lowest cell is 1.5V."""
593    logger.write_info_to_report("Testing Cell Imbalance Charge")
594    test_reset_cell_sims()
595    test_cell = bms_hardware.cells[1]
596    serial_watcher.start()
597    test_cell.disengage_safety_protocols = True
598    for cell in bms_hardware.cells.values():
599        cell.exact_volts = 4.2
600
601    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
602    logger.write_info_to_report(f"Resting at 0 mA, {', '.join(voltages)}")
603    serial_watcher.assert_true("flags.prefault_cellimbalance", False, 1)
604    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1)
605
606    bms_hardware.charger.set_profile(16.8, 1)
607    plateset.ce_switch = True
608    bms_hardware.charger.enable()
609    set_exact_volts(test_cell, 2.5)
610    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
611    logger.write_info_to_report(f"Charging at 1 A, {', '.join(voltages)}")
612    serial_watcher.assert_false("flags.prefault_cellimbalance", True)
613    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True)
614
615    test_cell.exact_volts = 4.2
616    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
617    logger.write_info_to_report(f"Resting at 0 mA, {', '.join(voltages)}")
618    serial_watcher.assert_false("flags.prefault_cellimbalance", True)
619    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True)
620
621    bms_hardware.charger.disable()
622    plateset.ce_switch = False
623    serial_watcher.stop()
624
625
626def test_current_limit():
627    """
628    | Description          | Confirm A fault is raised after 15A for 1 seconds                                    |
629    | :------------------- | :----------------------------------------------------------------------------------- |
630    | GitHub Issue         | turnaroundfactor/HITL#366                                                     |
631    | Instructions         | 1. Rest for 30 second                                                           </br>\
632                             2. Discharge at 2.25 amps for 1 second (the max limit will be 2amps)            </br>\
633                             3. Verify a prefault (before 1 second) and fault (after 1 second) are raised.   </br>\
634                             4. Rest until faults clear                                                      </br>\
635                             5. Discharge at 2.25 amps for less than 1 second                                </br>\
636                             6. Verify a prefault occurred but not a fault                                        |
637    | Pass / Fail Criteria | Pass if faults are raised after 1 second, but not before                             |
638    | Estimated Duration   | 5 minutes                                                                            |
639    | Notes                | We use 2A as a limit due to hardware limitations                                     |
640    """
641    # FIXME(JA): update duration after first test
642
643    logger.write_info_to_report("Testing Current Limit")
644    test_reset_cell_sims()
645    serial_watcher.start()
646
647    # Rest and make sure no faults are active
648    logger.write_info_to_report("Confirm no faults are active")
649    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 1)
650    serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", False, 1)
651
652    # Discharge at 2.25 amps for more than 1 second and verify faults are raised
653    logger.write_info_to_report("Discharging 2.25 A for more than 1 second")
654    with bms_hardware.load(2.25):
655        start_time = time.perf_counter()
656        serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", True, 2)
657        serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", True, 2)
658        assert serial_watcher.events["flags.fault_sw_overcurrent_discharge"][-1].time - start_time >= 1
659
660    # Rest until faults clear
661    logger.write_info_to_report("Resting")
662    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 3)
663    serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", False, 3)
664    assert (
665        serial_watcher.events["flags.fault_sw_overcurrent_discharge"][2].bms_time
666        - serial_watcher.events["flags.fault_sw_overcurrent_discharge"][1].bms_time
667    ).total_seconds() >= 20
668
669    # Discharge at 2.25 amps for less than 1 second, verify only prefault is raised
670    logger.write_info_to_report("Discharging 2.25 A for less than 1 second")
671    with bms_hardware.load(2.25):
672        time.sleep(0.5)
673    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", True, 4)
674    serial_watcher.assert_false("flags.fault_sw_overcurrent_discharge", True, 4)
675
676    # Rest until faults clear
677    logger.write_info_to_report("Resting")
678    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 5)
679
680    serial_watcher.stop()
CELL_VOLTAGE = 3.8002
def test_reset_cell_sims():
34def test_reset_cell_sims():
35    """Activate cell sims and set appropriate temperatures."""
36    logger.write_info_to_report("Powering down cell sims")
37    for cell in bms_hardware.cells.values():
38        cell.disengage_safety_protocols = True
39        cell.volts = 0.0001
40    time.sleep(5)
41    logger.write_info_to_report("Powering up cell sims")
42    for cell in bms_hardware.cells.values():
43        cell.volts = CELL_VOLTAGE
44        cell.disengage_safety_protocols = False
45    for cell in bms_hardware.cells.values():
46        cell.exact_volts = CELL_VOLTAGE
47    logger.write_info_to_report("Setting temperature to 15°C")
48    plateset.thermistor1 = 15
49    plateset.thermistor2 = 15
50    logger.write_info_to_report("Enabling faults")
51    time.sleep(1)
52    smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, smbus_types.BMSCommands.FAULT_ENABLE)  # Enable faults

Activate cell sims and set appropriate temperatures.

def test_wakeup_1():
55def test_wakeup_1():
56    """
57    The BMS should be in a state of slumber if the current measured is between -46mA and 19ma. This is done using
58    internal comparators on the board to a logical AND chip feeding into an interrupt pin. To test this, 3 different
59    currents should be set. One current below -46ma, another current between -46mA and 19ma, and another current above
60    19ma. If the current is within the allowable range, we should read logic 1 on the N_WAKEUP pin. If the current is
61    outside (above or below) we should read logic 0 on the pin.
62
63    # Test
64    - Default state at 0 mA (between -46mA and 19ma)
65      - n_wakeup_gpio = 1
66    - Discharging at 100 mA (below -46 mA)
67      - n_wakeup_gpio = 0
68    - Discharging at 10 mA (between -46mA and 19ma)
69      - n_wakeup_gpio = 1
70    - Charging at 100 mA (above 19 mA)
71      - n_wakeup_gpio = 0
72    """
73    logger.write_info_to_report("Testing Wakeup")
74    test_reset_cell_sims()
75    serial_watcher.start()
76
77    logger.write_info_to_report("Default state")
78    serial_watcher.assert_true("n_wakeup_gpio", True, 1)
79
80    logger.write_info_to_report("Discharging 100 mA")
81    bms_hardware.load.mode_cc()
82    bms_hardware.load.amps = 0.100
83    bms_hardware.load.enable()
84    serial_watcher.assert_true("n_wakeup_gpio", False, 2)
85
86    logger.write_info_to_report("Discharging 10 mA")
87    bms_hardware.load.amps = 0.010
88    serial_watcher.assert_true("n_wakeup_gpio", True, 3)
89    bms_hardware.load.disable()
90
91    logger.write_info_to_report("Charging 100 mA")
92    bms_hardware.charger.set_profile(16.8, 0.100)
93    plateset.ce_switch = True
94    bms_hardware.charger.enable()
95    serial_watcher.assert_true("n_wakeup_gpio", False, 4)
96    bms_hardware.charger.disable()
97    plateset.ce_switch = False
98
99    serial_watcher.stop()

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.

Test

  • Default state at 0 mA (between -46mA and 19ma)
    • n_wakeup_gpio = 1
  • Discharging at 100 mA (below -46 mA)
    • n_wakeup_gpio = 0
  • Discharging at 10 mA (between -46mA and 19ma)
    • n_wakeup_gpio = 1
  • Charging at 100 mA (above 19 mA)
    • n_wakeup_gpio = 0
def test_oc_oneshot():
102def test_oc_oneshot():
103    """
104    OC Oneshot: This is an interrupt that will trigger if we sink a ton of current into the battery, this cannot be
105    tested with just software and the comparators used for this have not been tested for hardware yet.
106
107    The bms should have an interrupt be fired when the current reaches the max amount (I think ~63 amps). this will be
108    a test for Tyler to do on his own
109
110    NOTE: High current. Only run this on a modified board.
111    """

OC Oneshot: This is an interrupt that will trigger if we sink a ton of current into the battery, this cannot be tested with just software and the comparators used for this have not been tested for hardware yet.

The bms should have an interrupt be fired when the current reaches the max amount (I think ~63 amps). this will be a test for Tyler to do on his own

NOTE: High current. Only run this on a modified board.

def test_overtemp_faults():
114def test_overtemp_faults():
115    """
116    If our batteries get too hot, we must trigger a fault. This is common during high discharge or charging cycles.
117    There are 3 different environments where we would trigger an overtemp fault: charge, discharge and resting. While
118    charging, if we are above 45C we must trigger a fault If we are resting or discharging at 59 degrees we must
119    trigger a fault. Both of these faults should trigger a prefault condition in our flags. After we cycle again we
120    should then trigger a full fault. If the temperature ever goes above 93 degrees, the fault should never clear and
121    we should be in permanent fault and trigger the fets. (This should also be seen in the flags)
122
123    # Test Overview (reset inbetween tests)
124    - Default state at 15°C
125      - prefault_overtemp_discharge = 0
126      - fault_overtemp_discharge = 0
127      - prefault_overtemp_charge = 0
128      - fault_overtemp_charge = 0
129      - permanentdisable_overtemp = 0
130      - measure_output_fets_disabled = 0
131
132    - Resting at 59°C (at or above 59°C)
133      - prefault_overtemp_discharge = 1
134      - fault_overtemp_discharge = 1
135
136    - Discharging at 100 mA, 59°C (at or above 59°C)
137      - prefault_overtemp_discharge = 1
138      - fault_overtemp_discharge = 1
139
140    - Charging at 100 mA, 46°C (at or above 45°C)
141      - prefault_overtemp_charge = 1
142      - fault_overtemp_charge = 1permanentdisable_overtemp
143
144    - Charging at 100 mA, 94°C (at or above 93°C)
145      - permanentdisable_overtemp = 1
146
147    - Discharging at 100 mA, 94°C (at or above 93°C)
148      - permanentdisable_overtemp = 1
149
150    - Resting at 94°C (at or above 93°C)
151      - permanentdisable_overtemp = 1
152    """
153    logger.write_info_to_report("Testing Overtemp")
154    test_reset_cell_sims()
155    serial_watcher.start()
156
157    # Test default state
158    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
159    plateset.thermistor1 = 15
160    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 1)
161    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 1)
162    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1)
163    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1)
164    serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1)
165    serial_watcher.assert_true("flags.measure_output_fets_disabled", False, 1)
166
167    # Test resting overtemp
168    logger.write_info_to_report("Resting at 59°C")
169    plateset.thermistor1 = 59
170    serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 2)
171    serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 2)
172    logger.write_info_to_report("Resting at 15°C")
173    plateset.thermistor1 = 15
174    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 3)
175    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 3)
176
177    # Test discharging overtemp
178    logger.write_info_to_report("Discharging at -100 mA, 59°C")
179    bms_hardware.load.mode_cc()
180    bms_hardware.load.amps = 0.100
181    bms_hardware.load.enable()
182    plateset.thermistor1 = 59
183    serial_watcher.assert_true("flags.prefault_overtemp_discharge", True, 4)
184    serial_watcher.assert_true("flags.fault_overtemp_discharge", True, 4)
185    logger.write_info_to_report("Discharging at -100 mA, 15°C")
186    plateset.thermistor1 = 15
187    serial_watcher.assert_true("flags.prefault_overtemp_discharge", False, 5)
188    serial_watcher.assert_true("flags.fault_overtemp_discharge", False, 5)
189    bms_hardware.load.disable()
190
191    # Test charging overtemp
192    logger.write_info_to_report("Charging at 100 mA, 65°C")
193    bms_hardware.charger.set_profile(16.8, 0.1)
194    plateset.ce_switch = True
195    bms_hardware.charger.enable()
196    plateset.thermistor1 = 65
197    serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2)
198    serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2)
199    logger.write_info_to_report("Charging at 100 mA, 45°C")
200    plateset.thermistor1 = 45
201    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3)
202    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3)
203    bms_hardware.charger.disable()
204    plateset.ce_switch = False
205
206    # Test charging permanent disable
207    logger.write_info_to_report("Charging at 100 mA, 94°C")
208    plateset.disengage_safety_protocols = True
209    plateset.thermistor1 = 94
210    plateset.disengage_safety_protocols = False
211    bms_hardware.charger.set_profile(16.8, 0.1)
212    plateset.ce_switch = True
213    bms_hardware.charger.enable()
214
215    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2)
216
217    logger.write_info_to_report("Charging at 100 mA, 15°C")
218    plateset.thermistor1 = 15
219    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3)
220
221    bms_hardware.charger.disable()
222    plateset.ce_switch = False
223    test_reset_cell_sims()
224
225    # Test discharging permanent disable
226    logger.write_info_to_report("Discharging at -100 mA, 94°C")
227    plateset.disengage_safety_protocols = True
228    plateset.thermistor1 = 94
229    plateset.disengage_safety_protocols = False
230    bms_hardware.load.mode_cc()
231    bms_hardware.load.amps = 0.100
232    bms_hardware.load.enable()
233
234    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 4)
235
236    logger.write_info_to_report("Discharging at -100 mA, 15°C")
237    plateset.thermistor1 = 15
238    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 5)
239
240    bms_hardware.load.disable()
241    test_reset_cell_sims()
242
243    # Test resting permanent disable
244    plateset.disengage_safety_protocols = True
245    logger.write_info_to_report("Resting at 94°C")
246    plateset.thermistor1 = 94
247    plateset.disengage_safety_protocols = False
248    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 6)
249
250    logger.write_info_to_report("Resting at 15°C")
251    plateset.thermistor1 = 15
252    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 7)
253
254    serial_watcher.stop()

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 45C 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)

Test Overview (reset inbetween tests)

  • Default state at 15°C

    • prefault_overtemp_discharge = 0
    • fault_overtemp_discharge = 0
    • prefault_overtemp_charge = 0
    • fault_overtemp_charge = 0
    • permanentdisable_overtemp = 0
    • measure_output_fets_disabled = 0
  • Resting at 59°C (at or above 59°C)

    • prefault_overtemp_discharge = 1
    • fault_overtemp_discharge = 1
  • Discharging at 100 mA, 59°C (at or above 59°C)

    • prefault_overtemp_discharge = 1
    • fault_overtemp_discharge = 1
  • Charging at 100 mA, 46°C (at or above 45°C)

    • prefault_overtemp_charge = 1
    • fault_overtemp_charge = 1permanentdisable_overtemp
  • Charging at 100 mA, 94°C (at or above 93°C)

    • permanentdisable_overtemp = 1
  • Discharging at 100 mA, 94°C (at or above 93°C)

    • permanentdisable_overtemp = 1
  • Resting at 94°C (at or above 93°C)

    • permanentdisable_overtemp = 1
def test_undertemp_faults():
257def test_undertemp_faults():
258    """
259    This occurs when we read more than 20 mamps from the battery, if any of the cells are under 0 degrees Celsius this
260    will trigger a fault. This will be cleared if we go above -2C. Regardless of current being measured, if we ever
261    read below -20C, this should trigger a fault. This fault should not be cleared until we are above -18C
262
263    *** THIS FAULT NEEDS TO BE INVESTIGATED
264    https://github.com/orgs/turnaroundfactor/projects/24?pane=issue&itemId=50150194
265    """
266    logger.write_info_to_report("Testing Undertemp")
267    test_reset_cell_sims()
268    serial_watcher.start()
269
270    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
271    plateset.thermistor1 = 15
272
273    # Discharging flags
274    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1)
275    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1)
276
277    # Charging flags
278    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1)
279    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1)
280
281    logger.write_info_to_report("Resting at -21°C")
282    plateset.thermistor1 = -21
283    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2)
284    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2)
285    logger.write_info_to_report("Resting at -17°C")
286    plateset.thermistor1 = -17
287    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3)
288    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3)
289
290    logger.write_info_to_report("Discharging at -100 mA, -21°C")
291    bms_hardware.load.mode_cc()
292    bms_hardware.load.amps = 0.100
293    bms_hardware.load.enable()
294    plateset.thermistor1 = -21
295    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 4)
296    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 4)
297    logger.write_info_to_report("Discharging at -100 mA, -17°C")
298    plateset.thermistor1 = -17
299    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 5)
300    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 5)
301    bms_hardware.load.disable()
302
303    logger.write_info_to_report("Charging at 100 mA, -25°C")
304    bms_hardware.charger.set_profile(16.8, 0.1)
305    plateset.ce_switch = True
306    bms_hardware.charger.enable()
307    plateset.thermistor1 = -25
308    serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2)
309    serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2)
310    logger.write_info_to_report("Charging at 100 mA, -15°C")
311    plateset.thermistor1 = -15
312    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3)
313    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
314    bms_hardware.charger.disable()
315    plateset.ce_switch = False
316
317    serial_watcher.stop()

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

* THIS FAULT NEEDS TO BE INVESTIGATED https://github.com/orgs/turnaroundfactor/projects/24?pane=issue&itemId=50150194

def set_exact_volts( cell: hitl_tester.modules.bms.cell.Cell, voltage: float, compensation: float = 0.08):
320def set_exact_volts(cell: Cell, voltage: float, compensation: float = 0.08):
321    """What the BMS reads won't exactly match the set voltage, thus we need slight adjustments."""
322    cell.exact_volts = voltage + compensation
323    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.

def test_overvoltage_faults():
326def test_overvoltage_faults():
327    """
328    While charging, we need to monitor the voltage of our cells. Specifically, if a cell ever goes above 4.205 Volts,
329    we should trigger a prefault. If this prefault exsists for more than 3 seconds, we then should trigger a full
330    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
331    should be able to clear the fault
332    """
333    logger.write_info_to_report("Testing Overvoltage")
334    test_reset_cell_sims()
335    test_cell = bms_hardware.cells[1]
336    serial_watcher.start()
337    test_cell.disengage_safety_protocols = True
338
339    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
340    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1)
341    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1)
342    serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1)
343
344    bms_hardware.charger.set_profile(16.8, 0.1)
345    plateset.ce_switch = True
346    bms_hardware.charger.enable()
347    logger.write_info_to_report("Charging at 100 mA, 4.21 V")
348    set_exact_volts(test_cell, 4.21)
349    serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2)
350    serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2)
351
352    logger.write_info_to_report("Charging at 100 mA, 4.10 V")
353    set_exact_volts(test_cell, 4.10)
354    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3)
355    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3)
356
357    logger.write_info_to_report("Charging at 100 mA, 4.26 V")
358    set_exact_volts(test_cell, 4.26)
359    serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2)
360
361    logger.write_info_to_report("Charging at 100 mA, 4.10 V")
362    set_exact_volts(test_cell, 4.10)
363    serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3)
364
365    bms_hardware.charger.disable()
366    plateset.ce_switch = False
367
368    serial_watcher.stop()

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

def test_undervoltage_faults():
371def test_undervoltage_faults():
372    """
373    This has also been validated in software, meaning the logic should properly handle a situation with a cell
374    discharging too low, however this has not yet been tested in hardware with a cell sensor reading that low of a
375    voltage and triggering a fault.
376
377    If we are reading less than 20mamps from the cells, we should be able to trigger an under-voltage fault. If we
378    read less than 2.4 volts, we must trigger a fault. If this fault persists for over 1 second, we should then trigger
379    a full fault. We will not clear this fault unless we are able to read above 2.5 volts.
380    If we are reading over 400mamps and a cell reads less than 2.325 volts, we must trigger a cell voltage charge min
381    prefault, if this persists for another bms software cycle we will trigger a full fault. This fault will clear when
382    we read above this voltage. If the cell voltage ever goes under 2.3 while charging, we must trigger a permanent
383    fault.
384    """
385    logger.write_info_to_report("Testing Undervoltage")
386    test_reset_cell_sims()
387    test_reset_cell_sims()
388    test_cell = bms_hardware.cells[1]
389    for cell in bms_hardware.cells.values():
390        cell.disengage_safety_protocols = True
391        set_exact_volts(cell, 3.0, 0.05)  # Must be low enough to not trigger cell imbalance [abs(high - low) > 1.5V]
392    serial_watcher.start()
393
394    logger.write_info_to_report("Resting at 0 mA, 3.0 V")
395    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1)
396    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1)
397    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1)
398    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1)
399    serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1)
400
401    logger.write_info_to_report("Resting at 0 mA, 2.3 V")
402    set_exact_volts(test_cell, 2.3, 0.05)
403    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2, wait_time=600)
404    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2, wait_time=600)
405
406    logger.write_info_to_report("Resting at 0 mA, 2.6 V")
407    set_exact_volts(test_cell, 2.6, 0.05)
408    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3, wait_time=600)
409    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3, wait_time=600)
410
411    bms_hardware.charger.set_profile(16.8, 0.500)
412    plateset.ce_switch = True
413    bms_hardware.charger.enable()
414    logger.write_info_to_report("Charging at 500 mA, 2.3 V")
415    set_exact_volts(test_cell, 2.3, 0.01)
416    serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2, wait_time=600)
417    serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2, wait_time=600)
418
419    logger.write_info_to_report("Charging at 500 mA, 2.4 V")
420    set_exact_volts(test_cell, 2.4, 0.05)
421    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3)
422    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3)
423
424    logger.write_info_to_report("Charging at 500 mA, 2.2 V")
425    set_exact_volts(test_cell, 2.2, 0.05)
426    serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2)
427
428    logger.write_info_to_report("Charging at 500 mA, 2.6 V")
429    set_exact_volts(test_cell, 2.6, 0.05)
430    serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3)
431
432    bms_hardware.charger.disable()
433    plateset.ce_switch = False
434
435    serial_watcher.stop()

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 a 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 400mamps 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.

def test_cell_imbalance():
438def test_cell_imbalance():
439    """Occurs when the difference between the highest and lowest cell is 1.5V."""
440    logger.write_info_to_report("Testing Cell Imbalance")
441    test_reset_cell_sims()
442    test_cell = bms_hardware.cells[1]
443    serial_watcher.start()
444    test_cell.disengage_safety_protocols = True
445
446    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
447    serial_watcher.assert_true("flags.prefault_cellimbalance", False, 1)
448    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1)
449
450    logger.write_info_to_report("Resting at 0 mA, 2.0 V")
451    set_exact_volts(test_cell, 2.0)
452    serial_watcher.assert_true("flags.prefault_cellimbalance", True, 2)
453    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", True, 2)
454
455    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V")
456    set_exact_volts(test_cell, CELL_VOLTAGE)
457    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", False, 3)
458
459    serial_watcher.stop()

Occurs when the difference between the highest and lowest cell is 1.5V.

def test_overvoltage_overtemp_faults():
462def test_overvoltage_overtemp_faults():
463    """A combo test where we have a high temperature and high voltage."""
464    logger.write_info_to_report("Testing Overvoltage with Overtemp")
465    test_reset_cell_sims()
466    test_cell = bms_hardware.cells[1]
467    serial_watcher.start()
468    test_cell.disengage_safety_protocols = True
469
470    logger.write_info_to_report(f"Resting at 0 mA, {CELL_VOLTAGE} V, 15°C")
471    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 1)
472    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 1)
473    serial_watcher.assert_true("flags.permanentdisable_overvoltage", False, 1)
474
475    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 1)
476    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 1)
477    serial_watcher.assert_true("flags.permanentdisable_overtemp", False, 1)
478
479    bms_hardware.charger.set_profile(16.8, 0.1)
480    plateset.ce_switch = True
481    bms_hardware.charger.enable()
482    logger.write_info_to_report("Charging at 100 mA, 4.21 V, 65°C")
483    set_exact_volts(test_cell, 4.21)
484    plateset.thermistor1 = 65
485    serial_watcher.assert_true("flags.prefault_overvoltage_charge", True, 2)
486    serial_watcher.assert_true("flags.fault_overvoltage_charge", True, 2)
487    serial_watcher.assert_true("flags.prefault_overtemp_charge", True, 2)
488    serial_watcher.assert_true("flags.fault_overtemp_charge", True, 2)
489
490    logger.write_info_to_report("Charging at 100 mA, 4.10 V, 45°C")
491    set_exact_volts(test_cell, 4.10)
492    plateset.thermistor1 = 45
493    serial_watcher.assert_true("flags.prefault_overvoltage_charge", False, 3)
494    serial_watcher.assert_true("flags.fault_overvoltage_charge", False, 3)
495    serial_watcher.assert_true("flags.prefault_overtemp_charge", False, 3)
496    serial_watcher.assert_true("flags.fault_overtemp_charge", False, 3)
497
498    logger.write_info_to_report("Charging at 100 mA, 4.26 V, 94°C")
499    set_exact_volts(test_cell, 4.26)
500    plateset.disengage_safety_protocols = True
501    plateset.thermistor1 = 94
502    plateset.disengage_safety_protocols = False
503    serial_watcher.assert_true("flags.permanentdisable_overvoltage", True, 2)
504    serial_watcher.assert_true("flags.permanentdisable_overtemp", True, 2)
505
506    logger.write_info_to_report("Charging at 100 mA, 4.10 V, 15°C")
507    set_exact_volts(test_cell, 4.10)
508    plateset.thermistor1 = 15
509    serial_watcher.assert_false("flags.permanentdisable_overvoltage", False, 3)
510    serial_watcher.assert_false("flags.permanentdisable_overtemp", False, 3)
511
512    bms_hardware.charger.disable()
513    plateset.ce_switch = False
514
515    serial_watcher.stop()

A combo test where we have a high temperature and high voltage.

def test_undervoltage_undertemp_faults():
518def test_undervoltage_undertemp_faults():
519    """A combo test where we have a low temperature and low voltage."""
520    logger.write_info_to_report("Testing Undervoltage with Undertemp")
521    test_reset_cell_sims()
522    test_cell = bms_hardware.cells[1]
523    for cell in bms_hardware.cells.values():
524        cell.disengage_safety_protocols = True
525        set_exact_volts(cell, 3.0)  # Must be low enough to not trigger cell imbalance [abs(high - low) > 1.5V]
526    serial_watcher.start()
527
528    logger.write_info_to_report("Resting at 0 mA, 3.0 V, 15°C")
529    plateset.thermistor1 = 15
530    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 1)
531    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 1)
532    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 1)
533    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 1)
534    serial_watcher.assert_true("flags.permanentdisable_undervoltage", False, 1)
535
536    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 1)
537    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 1)
538    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 1)
539    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 1)
540
541    logger.write_info_to_report("Resting at 0 mA, 2.3 V, -21°C")
542    set_exact_volts(test_cell, 2.3)
543    plateset.thermistor1 = -21
544    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", True, 2)
545    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", True, 2)
546    serial_watcher.assert_true("flags.prefault_undertemp_discharge", True, 2)
547    serial_watcher.assert_true("flags.fault_undertemp_discharge", True, 2)
548
549    logger.write_info_to_report("Resting at 0 mA, 2.6 V, -17°C")
550    set_exact_volts(test_cell, 2.6)
551    plateset.thermistor1 = -17
552    serial_watcher.assert_true("flags.prefault_undervoltage_discharge", False, 3)
553    serial_watcher.assert_true("flags.faultslumber_undervoltage_discharge", False, 3)
554    serial_watcher.assert_true("flags.prefault_undertemp_discharge", False, 3)
555    serial_watcher.assert_true("flags.fault_undertemp_discharge", False, 3)
556
557    bms_hardware.charger.set_profile(16.8, 0.500)
558    plateset.ce_switch = True
559    bms_hardware.charger.enable()
560    logger.write_info_to_report("Charging at 500 mA, 2.3 V, -25°C")
561    set_exact_volts(test_cell, 2.3, 0.01)
562    plateset.thermistor1 = -25
563    serial_watcher.assert_true("flags.prefault_undervoltage_charge", True, 2)
564    serial_watcher.assert_true("flags.fault_undervoltage_charge", True, 2)
565    serial_watcher.assert_true("flags.prefault_undertemp_charge", True, 2)
566    serial_watcher.assert_true("flags.fault_undertemp_charge", True, 2)
567
568    logger.write_info_to_report("Charging at 500 mA, 2.4 V, -15°C")
569    set_exact_volts(test_cell, 2.4)
570    plateset.thermistor1 = -15
571    serial_watcher.assert_true("flags.prefault_undervoltage_charge", False, 3)
572    serial_watcher.assert_true("flags.fault_undervoltage_charge", False, 3)
573    serial_watcher.assert_true("flags.prefault_undertemp_charge", False, 3)
574    serial_watcher.assert_true("flags.fault_undertemp_charge", False, 3)
575
576    logger.write_info_to_report("Charging at 500 mA, 2.2 V, 15°C")
577    set_exact_volts(test_cell, 2.2)
578    plateset.thermistor1 = 15
579    serial_watcher.assert_true("flags.permanentdisable_undervoltage", True, 2)
580
581    logger.write_info_to_report("Charging at 500 mA, 2.6 V, 15°C")
582    set_exact_volts(test_cell, 2.6)
583    plateset.thermistor1 = 15
584    serial_watcher.assert_false("flags.permanentdisable_undervoltage", False, 3)
585
586    bms_hardware.charger.disable()
587    plateset.ce_switch = False
588
589    serial_watcher.stop()

A combo test where we have a low temperature and low voltage.

def test_cell_imbalance_charge():
592def test_cell_imbalance_charge():
593    """Occurs when the difference between the highest and lowest cell is 1.5V."""
594    logger.write_info_to_report("Testing Cell Imbalance Charge")
595    test_reset_cell_sims()
596    test_cell = bms_hardware.cells[1]
597    serial_watcher.start()
598    test_cell.disengage_safety_protocols = True
599    for cell in bms_hardware.cells.values():
600        cell.exact_volts = 4.2
601
602    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
603    logger.write_info_to_report(f"Resting at 0 mA, {', '.join(voltages)}")
604    serial_watcher.assert_true("flags.prefault_cellimbalance", False, 1)
605    serial_watcher.assert_true("flags.permanentdisable_cellimbalance", False, 1)
606
607    bms_hardware.charger.set_profile(16.8, 1)
608    plateset.ce_switch = True
609    bms_hardware.charger.enable()
610    set_exact_volts(test_cell, 2.5)
611    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
612    logger.write_info_to_report(f"Charging at 1 A, {', '.join(voltages)}")
613    serial_watcher.assert_false("flags.prefault_cellimbalance", True)
614    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True)
615
616    test_cell.exact_volts = 4.2
617    voltages = [f"{cell.measured_volts} V" for cell in bms_hardware.cells.values()]
618    logger.write_info_to_report(f"Resting at 0 mA, {', '.join(voltages)}")
619    serial_watcher.assert_false("flags.prefault_cellimbalance", True)
620    serial_watcher.assert_false("flags.permanentdisable_cellimbalance", True)
621
622    bms_hardware.charger.disable()
623    plateset.ce_switch = False
624    serial_watcher.stop()

Occurs when the difference between the highest and lowest cell is 1.5V.

def test_current_limit():
627def test_current_limit():
628    """
629    | Description          | Confirm A fault is raised after 15A for 1 seconds                                    |
630    | :------------------- | :----------------------------------------------------------------------------------- |
631    | GitHub Issue         | turnaroundfactor/HITL#366                                                     |
632    | Instructions         | 1. Rest for 30 second                                                           </br>\
633                             2. Discharge at 2.25 amps for 1 second (the max limit will be 2amps)            </br>\
634                             3. Verify a prefault (before 1 second) and fault (after 1 second) are raised.   </br>\
635                             4. Rest until faults clear                                                      </br>\
636                             5. Discharge at 2.25 amps for less than 1 second                                </br>\
637                             6. Verify a prefault occurred but not a fault                                        |
638    | Pass / Fail Criteria | Pass if faults are raised after 1 second, but not before                             |
639    | Estimated Duration   | 5 minutes                                                                            |
640    | Notes                | We use 2A as a limit due to hardware limitations                                     |
641    """
642    # FIXME(JA): update duration after first test
643
644    logger.write_info_to_report("Testing Current Limit")
645    test_reset_cell_sims()
646    serial_watcher.start()
647
648    # Rest and make sure no faults are active
649    logger.write_info_to_report("Confirm no faults are active")
650    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 1)
651    serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", False, 1)
652
653    # Discharge at 2.25 amps for more than 1 second and verify faults are raised
654    logger.write_info_to_report("Discharging 2.25 A for more than 1 second")
655    with bms_hardware.load(2.25):
656        start_time = time.perf_counter()
657        serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", True, 2)
658        serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", True, 2)
659        assert serial_watcher.events["flags.fault_sw_overcurrent_discharge"][-1].time - start_time >= 1
660
661    # Rest until faults clear
662    logger.write_info_to_report("Resting")
663    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 3)
664    serial_watcher.assert_true("flags.fault_sw_overcurrent_discharge", False, 3)
665    assert (
666        serial_watcher.events["flags.fault_sw_overcurrent_discharge"][2].bms_time
667        - serial_watcher.events["flags.fault_sw_overcurrent_discharge"][1].bms_time
668    ).total_seconds() >= 20
669
670    # Discharge at 2.25 amps for less than 1 second, verify only prefault is raised
671    logger.write_info_to_report("Discharging 2.25 A for less than 1 second")
672    with bms_hardware.load(2.25):
673        time.sleep(0.5)
674    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", True, 4)
675    serial_watcher.assert_false("flags.fault_sw_overcurrent_discharge", True, 4)
676
677    # Rest until faults clear
678    logger.write_info_to_report("Resting")
679    serial_watcher.assert_true("flags.prefault_sw_overcurrent_discharge", False, 5)
680
681    serial_watcher.stop()
Description Confirm A fault is raised after 15A for 1 seconds
GitHub Issue turnaroundfactor/HITL#366
Instructions 1. Rest for 30 second
2. Discharge at 2.25 amps for 1 second (the max limit will be 2amps)
3. Verify a prefault (before 1 second) and fault (after 1 second) are raised.
4. Rest until faults clear
5. Discharge at 2.25 amps for less than 1 second
6. Verify a prefault occurred but not a fault
Pass / Fail Criteria Pass if faults are raised after 1 second, but not before
Estimated Duration 5 minutes
Notes We use 2A as a limit due to hardware limitations