hitl_tester.test_cases.bms.firmware_flash_save

Test Save flash when reprogrammed
GitHub Issue(s) turnaroundfactor/HITL#327
turnaroundfactor/HITL#342
Description Don't overwrite calibration/faults when reprogramming
  1"""
  2| Test                 | Save flash when reprogrammed                                 |
  3| :------------------- | :----------------------------------------------------------- |
  4| GitHub Issue(s)      | turnaroundfactor/HITL#327                        </br>\
  5                         turnaroundfactor/HITL#342                             |
  6| Description          | Don't overwrite calibration/faults when reprogramming        |
  7"""
  8
  9from __future__ import annotations
 10
 11import ctypes
 12import time
 13
 14import pytest
 15
 16from hitl_tester.modules.bms.adc_plate import ADCPlate
 17from hitl_tester.modules.bms.bms_hw import BMSHardware
 18from hitl_tester.modules.bms.bms_serial import serial_monitor
 19from hitl_tester.modules.bms.plateset import Plateset
 20from hitl_tester.modules.bms.smbus import SMBus
 21from hitl_tester.modules.bms.smbus_types import SMBusReg, BMSCommands
 22from hitl_tester.modules.logger import logger
 23from hitl_tester.test_cases.bms.test_flash_firmware import test_flash_firmware
 24
 25FLASH_SLEEP = 7
 26"""Time to wait for flash to write."""
 27
 28_bms = BMSHardware(pytest.flags)  # type: ignore[arg-type]
 29_bms.init()
 30_plateset = Plateset()
 31_adc_plate = ADCPlate()
 32_smbus = SMBus()
 33
 34
 35def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10):
 36    """Helper function to stabilize the batteries for 2+ hours."""
 37    _bms.max_time = seconds
 38    _bms.sample_interval = sample_interval
 39    _bms.run_resting_cycle()
 40
 41
 42@pytest.mark.sim_cells
 43class TestFlashPreservation:
 44    """Confirm flash is preserved."""
 45
 46    average = 0
 47    readings = 0
 48
 49    def bms_current(self):
 50        """Measure serial current and calculate an average."""
 51        new_reading = _bms.csv.cycle.last_serial_data["mamps"]
 52        self.average = (new_reading + self.readings * self.average) / (self.readings + 1)
 53        self.readings += 1
 54        logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}")  # Output current on every sample
 55
 56    def calibrate_current(self):
 57        """Calibrate BMS current in flash."""
 58        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
 59        self.readings = 0
 60        standard_rest(30, 5)
 61        logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA")
 62
 63        # Calibrate
 64        offset = int(round(self.average, 0))
 65        logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA")
 66        data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE
 67        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data)
 68        time.sleep(FLASH_SLEEP)
 69        data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]
 70        logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}")
 71
 72        _bms.csv.cycle.postfix_fn = lambda: ...
 73
 74    def is_calibrated(self) -> bool:
 75        """Confirm current is in acceptable range"""
 76        acceptable_error_ma = 5
 77
 78        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
 79        self.readings = 0  # Reset average
 80        standard_rest(30, 5)
 81        logger.write_result_to_html_report(f"Average rest current (calibrated?): {self.average:.3f} mA")
 82        return acceptable_error_ma > self.average > -acceptable_error_ma
 83
 84    def enable_faults(self):
 85        """Enable faults via SMBus."""
 86        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.FAULT_ENABLE)
 87        time.sleep(FLASH_SLEEP)
 88
 89    def is_faults_enabled(self) -> bool:
 90        """Check if overtemp faults can be raised."""
 91        timeout_s = 10
 92
 93        # Raise a fault
 94        _plateset.thermistor1 = 65
 95        start = time.perf_counter()
 96        while (serial_data := serial_monitor.read(latest=True)) and not serial_data["flags.fault_overtemp_discharge"]:
 97            if time.perf_counter() - start > timeout_s:
 98                logger.write_debug_to_report(f"Over-temperature fault was not raised after {timeout_s} seconds.")
 99                return False
100
101        # Clear the fault
102        _plateset.thermistor1 = 45
103        start = time.perf_counter()
104        while (serial_data := serial_monitor.read(latest=True)) and serial_data["flags.fault_overtemp_discharge"]:
105            if time.perf_counter() - start > timeout_s:
106                logger.write_debug_to_report(f"Over-temperature fault was not cleared after {timeout_s} seconds.")
107                return False
108
109        return True
110
111    def test_flash_preservation(self):
112        """
113        | Description          | Confirm calibration/faults are preserved when reprogrammed             |
114        | :------------------- | :--------------------------------------------------------------------- |
115        | GitHub Issue         | turnaroundfactor/HITL#347                                       |
116        | Instructions         | 1. Set up flash values                                            </br>\
117                                  ⠀⠀⦁ Calibrate the BMS and verify                                 </br>\
118                                  ⠀⠀⦁ Enable the faults and verify                                 </br>\
119                                 2. Flash new firmware                                             </br>\
120                                 3. Confirm BMS calibration/faults are not erased                  </br>\
121                                  ⠀⠀⦁ BMS is calibrated                                            </br>\
122                                  ⠀⠀⦁ Faults are enabled                                                |
123        | Pass / Fail Criteria | Pass if flash is preserved                                             |
124        | Estimated Duration   | 1 minute                                                               |
125        """
126
127        # 1. Set up flash values, confirm they're set
128        self.calibrate_current()
129        self.enable_faults()
130        assert self.is_calibrated(), "Current was not calibrated."
131        assert self.is_faults_enabled(), "Faults were not enabled."
132
133        # 2/3. Erase flash, confirm BMS flash is erased
134        test_flash_firmware()
135        logger.write_info_to_report("Sleeping for 10 seconds before starting...")
136        time.sleep(10)
137        assert self.is_calibrated(), "Current was not calibrated after firmware flash."
138        assert self.is_faults_enabled(), "Faults were not enabled after firmware flash."
FLASH_SLEEP = 7

Time to wait for flash to write.

def standard_rest(seconds: float = 7200, sample_interval: int = 10):
36def standard_rest(seconds: float = 2 * 3600, sample_interval: int = 10):
37    """Helper function to stabilize the batteries for 2+ hours."""
38    _bms.max_time = seconds
39    _bms.sample_interval = sample_interval
40    _bms.run_resting_cycle()

Helper function to stabilize the batteries for 2+ hours.

@pytest.mark.sim_cells
class TestFlashPreservation:
 43@pytest.mark.sim_cells
 44class TestFlashPreservation:
 45    """Confirm flash is preserved."""
 46
 47    average = 0
 48    readings = 0
 49
 50    def bms_current(self):
 51        """Measure serial current and calculate an average."""
 52        new_reading = _bms.csv.cycle.last_serial_data["mamps"]
 53        self.average = (new_reading + self.readings * self.average) / (self.readings + 1)
 54        self.readings += 1
 55        logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}")  # Output current on every sample
 56
 57    def calibrate_current(self):
 58        """Calibrate BMS current in flash."""
 59        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
 60        self.readings = 0
 61        standard_rest(30, 5)
 62        logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA")
 63
 64        # Calibrate
 65        offset = int(round(self.average, 0))
 66        logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA")
 67        data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE
 68        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data)
 69        time.sleep(FLASH_SLEEP)
 70        data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]
 71        logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}")
 72
 73        _bms.csv.cycle.postfix_fn = lambda: ...
 74
 75    def is_calibrated(self) -> bool:
 76        """Confirm current is in acceptable range"""
 77        acceptable_error_ma = 5
 78
 79        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
 80        self.readings = 0  # Reset average
 81        standard_rest(30, 5)
 82        logger.write_result_to_html_report(f"Average rest current (calibrated?): {self.average:.3f} mA")
 83        return acceptable_error_ma > self.average > -acceptable_error_ma
 84
 85    def enable_faults(self):
 86        """Enable faults via SMBus."""
 87        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.FAULT_ENABLE)
 88        time.sleep(FLASH_SLEEP)
 89
 90    def is_faults_enabled(self) -> bool:
 91        """Check if overtemp faults can be raised."""
 92        timeout_s = 10
 93
 94        # Raise a fault
 95        _plateset.thermistor1 = 65
 96        start = time.perf_counter()
 97        while (serial_data := serial_monitor.read(latest=True)) and not serial_data["flags.fault_overtemp_discharge"]:
 98            if time.perf_counter() - start > timeout_s:
 99                logger.write_debug_to_report(f"Over-temperature fault was not raised after {timeout_s} seconds.")
100                return False
101
102        # Clear the fault
103        _plateset.thermistor1 = 45
104        start = time.perf_counter()
105        while (serial_data := serial_monitor.read(latest=True)) and serial_data["flags.fault_overtemp_discharge"]:
106            if time.perf_counter() - start > timeout_s:
107                logger.write_debug_to_report(f"Over-temperature fault was not cleared after {timeout_s} seconds.")
108                return False
109
110        return True
111
112    def test_flash_preservation(self):
113        """
114        | Description          | Confirm calibration/faults are preserved when reprogrammed             |
115        | :------------------- | :--------------------------------------------------------------------- |
116        | GitHub Issue         | turnaroundfactor/HITL#347                                       |
117        | Instructions         | 1. Set up flash values                                            </br>\
118                                  ⠀⠀⦁ Calibrate the BMS and verify                                 </br>\
119                                  ⠀⠀⦁ Enable the faults and verify                                 </br>\
120                                 2. Flash new firmware                                             </br>\
121                                 3. Confirm BMS calibration/faults are not erased                  </br>\
122                                  ⠀⠀⦁ BMS is calibrated                                            </br>\
123                                  ⠀⠀⦁ Faults are enabled                                                |
124        | Pass / Fail Criteria | Pass if flash is preserved                                             |
125        | Estimated Duration   | 1 minute                                                               |
126        """
127
128        # 1. Set up flash values, confirm they're set
129        self.calibrate_current()
130        self.enable_faults()
131        assert self.is_calibrated(), "Current was not calibrated."
132        assert self.is_faults_enabled(), "Faults were not enabled."
133
134        # 2/3. Erase flash, confirm BMS flash is erased
135        test_flash_firmware()
136        logger.write_info_to_report("Sleeping for 10 seconds before starting...")
137        time.sleep(10)
138        assert self.is_calibrated(), "Current was not calibrated after firmware flash."
139        assert self.is_faults_enabled(), "Faults were not enabled after firmware flash."

Confirm flash is preserved.

average = 0
readings = 0
def bms_current(self):
50    def bms_current(self):
51        """Measure serial current and calculate an average."""
52        new_reading = _bms.csv.cycle.last_serial_data["mamps"]
53        self.average = (new_reading + self.readings * self.average) / (self.readings + 1)
54        self.readings += 1
55        logger.write_info_to_report(f"BMS Serial Current (mA): {new_reading:.3f}")  # Output current on every sample

Measure serial current and calculate an average.

def calibrate_current(self):
57    def calibrate_current(self):
58        """Calibrate BMS current in flash."""
59        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
60        self.readings = 0
61        standard_rest(30, 5)
62        logger.write_result_to_html_report(f"Average rest current: {self.average:.3f} mA")
63
64        # Calibrate
65        offset = int(round(self.average, 0))
66        logger.write_info_to_report(f"Setting offset current to {offset:.3f} mA")
67        data = (ctypes.c_uint8(offset).value << 8) | BMSCommands.CALIBRATE
68        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, data)
69        time.sleep(FLASH_SLEEP)
70        data = _smbus.read_register(SMBusReg.MANUFACTURING_ACCESS)[0]
71        logger.write_info_to_report(f"{SMBusReg.MANUFACTURING_ACCESS.fname}: {data:04X}")
72
73        _bms.csv.cycle.postfix_fn = lambda: ...

Calibrate BMS current in flash.

def is_calibrated(self) -> bool:
75    def is_calibrated(self) -> bool:
76        """Confirm current is in acceptable range"""
77        acceptable_error_ma = 5
78
79        _bms.csv.cycle.postfix_fn = self.bms_current  # Get current on each sample
80        self.readings = 0  # Reset average
81        standard_rest(30, 5)
82        logger.write_result_to_html_report(f"Average rest current (calibrated?): {self.average:.3f} mA")
83        return acceptable_error_ma > self.average > -acceptable_error_ma

Confirm current is in acceptable range

def enable_faults(self):
85    def enable_faults(self):
86        """Enable faults via SMBus."""
87        _smbus.write_register(SMBusReg.MANUFACTURING_ACCESS, BMSCommands.FAULT_ENABLE)
88        time.sleep(FLASH_SLEEP)

Enable faults via SMBus.

def is_faults_enabled(self) -> bool:
 90    def is_faults_enabled(self) -> bool:
 91        """Check if overtemp faults can be raised."""
 92        timeout_s = 10
 93
 94        # Raise a fault
 95        _plateset.thermistor1 = 65
 96        start = time.perf_counter()
 97        while (serial_data := serial_monitor.read(latest=True)) and not serial_data["flags.fault_overtemp_discharge"]:
 98            if time.perf_counter() - start > timeout_s:
 99                logger.write_debug_to_report(f"Over-temperature fault was not raised after {timeout_s} seconds.")
100                return False
101
102        # Clear the fault
103        _plateset.thermistor1 = 45
104        start = time.perf_counter()
105        while (serial_data := serial_monitor.read(latest=True)) and serial_data["flags.fault_overtemp_discharge"]:
106            if time.perf_counter() - start > timeout_s:
107                logger.write_debug_to_report(f"Over-temperature fault was not cleared after {timeout_s} seconds.")
108                return False
109
110        return True

Check if overtemp faults can be raised.

def test_flash_preservation(self):
112    def test_flash_preservation(self):
113        """
114        | Description          | Confirm calibration/faults are preserved when reprogrammed             |
115        | :------------------- | :--------------------------------------------------------------------- |
116        | GitHub Issue         | turnaroundfactor/HITL#347                                       |
117        | Instructions         | 1. Set up flash values                                            </br>\
118                                  ⠀⠀⦁ Calibrate the BMS and verify                                 </br>\
119                                  ⠀⠀⦁ Enable the faults and verify                                 </br>\
120                                 2. Flash new firmware                                             </br>\
121                                 3. Confirm BMS calibration/faults are not erased                  </br>\
122                                  ⠀⠀⦁ BMS is calibrated                                            </br>\
123                                  ⠀⠀⦁ Faults are enabled                                                |
124        | Pass / Fail Criteria | Pass if flash is preserved                                             |
125        | Estimated Duration   | 1 minute                                                               |
126        """
127
128        # 1. Set up flash values, confirm they're set
129        self.calibrate_current()
130        self.enable_faults()
131        assert self.is_calibrated(), "Current was not calibrated."
132        assert self.is_faults_enabled(), "Faults were not enabled."
133
134        # 2/3. Erase flash, confirm BMS flash is erased
135        test_flash_firmware()
136        logger.write_info_to_report("Sleeping for 10 seconds before starting...")
137        time.sleep(10)
138        assert self.is_calibrated(), "Current was not calibrated after firmware flash."
139        assert self.is_faults_enabled(), "Faults were not enabled after firmware flash."
Description Confirm calibration/faults are preserved when reprogrammed
GitHub Issue turnaroundfactor/HITL#347
Instructions 1. Set up flash values
⠀⠀⦁ Calibrate the BMS and verify
⠀⠀⦁ Enable the faults and verify
2. Flash new firmware
3. Confirm BMS calibration/faults are not erased
⠀⠀⦁ BMS is calibrated
⠀⠀⦁ Faults are enabled
Pass / Fail Criteria Pass if flash is preserved
Estimated Duration 1 minute
pytestmark = [Mark(name='sim_cells', args=(), kwargs={})]