hitl_tester.test_cases.bms.test_current_calibration

Test Current Calibration
GitHub Issue(s) turnaroundfactor/HITL#367
Description Calibrate a unique BMS current with SMBus commands.

Used in these test plans:

  • calibrate_current ⠀⠀⠀(bms/calibrate_current.plan)

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

  • ./hitl_tester.py calibrate_current -DCELL_SOC_PERCENT=0.5 -DBMS_TEMPERATURE_C=23 -DCURRENT_OFFSET=None
  1"""
  2| Test                 | Current Calibration                                 |
  3| :------------------- | :-------------------------------------------------- |
  4| GitHub Issue(s)      | turnaroundfactor/HITL#367                    |
  5| Description          | Calibrate a unique BMS current with SMBus commands. |
  6"""
  7
  8from __future__ import annotations
  9
 10import ctypes
 11import time
 12
 13import pytest
 14
 15from hitl_tester.modules.bms.bms_hw import BMSHardware
 16from hitl_tester.modules.bms.plateset import Plateset
 17from hitl_tester.modules.bms.smbus import SMBus
 18from hitl_tester.modules.bms.smbus_types import BMSCommands, SMBusReg
 19from hitl_tester.modules.logger import logger
 20
 21CELL_SOC_PERCENT = 0.50
 22"""The cell sim SOC."""
 23
 24BMS_TEMPERATURE_C = 23
 25"""Simulated temperature."""
 26
 27CURRENT_OFFSET: int | None = None
 28"""The calibration value to upload."""
 29
 30_bms = BMSHardware(pytest.flags)  # type: ignore[arg-type]
 31_bms.init()
 32_plateset = Plateset()
 33_smbus = SMBus()
 34
 35
 36def bms_current():
 37    """Measure serial current and calculate an average."""
 38    new_reading = _bms.csv.cycle.last_serial_data["mamps"]
 39
 40    try:
 41        bms_current.average = (new_reading + bms_current.readings * bms_current.average) / (bms_current.readings + 1)
 42        bms_current.readings += 1
 43    except AttributeError:
 44        bms_current.average = new_reading
 45        bms_current.readings = 1
 46
 47    logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading}")  # Output current on every sample
 48
 49
 50@pytest.fixture(scope="function", autouse=True)
 51def reset_test_environment():
 52    """Before each test, reset cell sims / BMS and set appropriate temperatures."""
 53
 54    # Reset cell sims
 55    if len(_bms.cells) > 0:
 56        logger.write_info_to_report(f"Setting temperature to {BMS_TEMPERATURE_C}°C")
 57        _plateset.thermistor1 = _plateset.thermistor2 = BMS_TEMPERATURE_C
 58        logger.write_info_to_report("Powering down cell sims")
 59        for cell in _bms.cells.values():
 60            cell.disengage_safety_protocols = True
 61            cell.volts = 0.0001
 62        time.sleep(5)
 63        for cell in _bms.cells.values():
 64            logger.write_info_to_report(f"Powering up cell sim {cell.id} to {CELL_SOC_PERCENT:%}")
 65            cell.state_of_charge = CELL_SOC_PERCENT
 66            cell.disengage_safety_protocols = False
 67
 68    # Wait after powering on
 69    if len(_bms.cells) > 0:
 70        logger.write_info_to_report("Sleeping for 10 seconds before starting...")
 71        time.sleep(10)
 72
 73    old_cycle_function = _bms.csv.cycle  # Save logging function
 74    _bms.csv.cycle = _bms.csv.cycle_smbus  # Record serial and SMBus
 75    _bms.csv.cycle.postfix_fn = bms_current  # Get current on each sample
 76
 77    yield  # Run test
 78
 79    _bms.csv.cycle = old_cycle_function
 80
 81
 82def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10):
 83    """Helper function to stabilize the batteries for 2+ hours."""
 84    _bms.max_time = seconds
 85    _bms.sample_interval = sample_interval
 86    _bms.run_resting_cycle()
 87
 88
 89def test_calibrate():
 90    """
 91    | Requirement          | Write calibration current to BMS                                            |
 92    | :------------------- | :-------------------------------------------------------------------------- |
 93    | GitHub Issue         | turnaroundfactor/HITL#367                                            |
 94    | Instructions         | 1. Measure average current while resting                               </br>\
 95                             2. Write this value to the manufacturing register via the CALIBRATE command |
 96    | Pass / Fail Criteria | Current reads 0mA at rest                                                   |
 97    | Estimated Duration   | 2 minutes                                                                   |
 98    """
 99    acceptable_error_ma = 5
100
101    # Measure and apply offset
102    standard_rest(30, 5)
103    offset = int(round(bms_current.average if CURRENT_OFFSET is None else CURRENT_OFFSET, 0))
104    logger.write_info_to_report(f"Setting offset current to {offset} mA")
105    data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE
106    _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data)
107
108    data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]
109    logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}")
110
111    # Confirm current is in acceptable range
112    bms_current.readings = 0  # Reset average
113    standard_rest(30, 5)
114    assert acceptable_error_ma > bms_current.average > -acceptable_error_ma
CELL_SOC_PERCENT = 0.5

The cell sim SOC.

BMS_TEMPERATURE_C = 23

Simulated temperature.

CURRENT_OFFSET: int | None = None

The calibration value to upload.

def bms_current():
37def bms_current():
38    """Measure serial current and calculate an average."""
39    new_reading = _bms.csv.cycle.last_serial_data["mamps"]
40
41    try:
42        bms_current.average = (new_reading + bms_current.readings * bms_current.average) / (bms_current.readings + 1)
43        bms_current.readings += 1
44    except AttributeError:
45        bms_current.average = new_reading
46        bms_current.readings = 1
47
48    logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading}")  # Output current on every sample

Measure serial current and calculate an average.

@pytest.fixture(scope='function', autouse=True)
def reset_test_environment():
51@pytest.fixture(scope="function", autouse=True)
52def reset_test_environment():
53    """Before each test, reset cell sims / BMS and set appropriate temperatures."""
54
55    # Reset cell sims
56    if len(_bms.cells) > 0:
57        logger.write_info_to_report(f"Setting temperature to {BMS_TEMPERATURE_C}°C")
58        _plateset.thermistor1 = _plateset.thermistor2 = BMS_TEMPERATURE_C
59        logger.write_info_to_report("Powering down cell sims")
60        for cell in _bms.cells.values():
61            cell.disengage_safety_protocols = True
62            cell.volts = 0.0001
63        time.sleep(5)
64        for cell in _bms.cells.values():
65            logger.write_info_to_report(f"Powering up cell sim {cell.id} to {CELL_SOC_PERCENT:%}")
66            cell.state_of_charge = CELL_SOC_PERCENT
67            cell.disengage_safety_protocols = False
68
69    # Wait after powering on
70    if len(_bms.cells) > 0:
71        logger.write_info_to_report("Sleeping for 10 seconds before starting...")
72        time.sleep(10)
73
74    old_cycle_function = _bms.csv.cycle  # Save logging function
75    _bms.csv.cycle = _bms.csv.cycle_smbus  # Record serial and SMBus
76    _bms.csv.cycle.postfix_fn = bms_current  # Get current on each sample
77
78    yield  # Run test
79
80    _bms.csv.cycle = old_cycle_function

Before each test, reset cell sims / BMS and set appropriate temperatures.

def standard_rest(seconds: float = 7200, sample_interval: int = 10):
83def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10):
84    """Helper function to stabilize the batteries for 2+ hours."""
85    _bms.max_time = seconds
86    _bms.sample_interval = sample_interval
87    _bms.run_resting_cycle()

Helper function to stabilize the batteries for 2+ hours.

def test_calibrate():
 90def test_calibrate():
 91    """
 92    | Requirement          | Write calibration current to BMS                                            |
 93    | :------------------- | :-------------------------------------------------------------------------- |
 94    | GitHub Issue         | turnaroundfactor/HITL#367                                            |
 95    | Instructions         | 1. Measure average current while resting                               </br>\
 96                             2. Write this value to the manufacturing register via the CALIBRATE command |
 97    | Pass / Fail Criteria | Current reads 0mA at rest                                                   |
 98    | Estimated Duration   | 2 minutes                                                                   |
 99    """
100    acceptable_error_ma = 5
101
102    # Measure and apply offset
103    standard_rest(30, 5)
104    offset = int(round(bms_current.average if CURRENT_OFFSET is None else CURRENT_OFFSET, 0))
105    logger.write_info_to_report(f"Setting offset current to {offset} mA")
106    data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE
107    _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data)
108
109    data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]
110    logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}")
111
112    # Confirm current is in acceptable range
113    bms_current.readings = 0  # Reset average
114    standard_rest(30, 5)
115    assert acceptable_error_ma > bms_current.average > -acceptable_error_ma
Requirement Write calibration current to BMS
GitHub Issue turnaroundfactor/HITL#367
Instructions 1. Measure average current while resting
2. Write this value to the manufacturing register via the CALIBRATE command
Pass / Fail Criteria Current reads 0mA at rest
Estimated Duration 2 minutes