From 0802846b882142da21d16189af629102fca810b9 Mon Sep 17 00:00:00 2001 From: Ben Pirt Date: Sat, 2 Dec 2023 10:07:56 +0000 Subject: [PATCH] Dynamically adjust the charge current to avoid over-voltage --- README.md | 3 +- battery/battery_pack.py | 7 ----- bms/bms.py | 36 ++++++++++++++++------ test/bms/test_Bms.py | 66 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 test/bms/test_Bms.py diff --git a/README.md b/README.md index 7e7b533..c422105 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ Which will produce `build/out/tesla-bms-emulator.uf2`. You can drag and drop thi ## To Do -- [ ] Dynamically scale back the charge current to avoid going over voltage +- [ ] Add support for heating the battery pack in cold weather +- [x] Dynamically scale back the charge current to avoid going over voltage - [x] Add hysteresis on over voltage faults - [x] Simulator for integration testing over socat virtual serial port - [x] Cell balancing diff --git a/battery/battery_pack.py b/battery/battery_pack.py index ec68bcc..e3985d0 100644 --- a/battery/battery_pack.py +++ b/battery/battery_pack.py @@ -122,13 +122,6 @@ def cell_voltage_difference(self) -> float: return max((module.cell_voltage_difference for module in self.modules)) return 0.0 - def should_charge(self, current_state: bool) -> bool: - if current_state is True: - return self.high_cell_voltage < self._config.cell_high_voltage_setpoint - else: - return self.high_cell_voltage < ( - self._config.cell_high_voltage_setpoint - self._config.charge_hysteresis_voltage) - def should_discharge(self, current_state: bool) -> bool: if current_state is True: return self.low_cell_voltage > self._config.cell_low_voltage_setpoint diff --git a/bms/bms.py b/bms/bms.py index 858987e..fc108ac 100644 --- a/bms/bms.py +++ b/bms/bms.py @@ -26,10 +26,10 @@ def __init__(self, battery_pack: BatteryPack, config: Config, current_sensor: Op self.__interval = get_interval() self.__interval.set(self.__poll_interval) self.__led = Led(self.__config.led_pin) - self.__charging_enabled = True self.__discharging_enabled = True self.__charging_timer = get_interval() self.__charging_timer.set(self.__config.charge_hysteresis_time) + self.__charge_current = self.__config.max_charge_current self.__discharging_timer = get_interval() self.__discharging_timer.set(self.__config.charge_hysteresis_time) self.__wdt: Optional[WDT] = None @@ -73,17 +73,35 @@ def current(self) -> float: @property def charge_current_setpoint(self) -> float: - self.__charging_enabled = self.battery_pack.should_charge(self.__charging_enabled) - if self.__charging_enabled is True: - if self.__charging_timer.ready: - return self.__config.max_charge_current - else: - self.__charging_timer.reset() - return 0 + voltage_difference = self.battery_pack.high_cell_voltage - \ + self.__config.cell_high_voltage_setpoint + if voltage_difference > 0: + scale_factor = 0.5 + # Scale back the charge current in proportion to how much the voltage is over the threshold + # If the voltage difference is > 0.25V then set the charge current to 50% of actual + max_difference = 0.25 + most_reduction = 0.5 + # If the voltage is not much over (0.025V), then set the charge current to 90% of actual + min_difference = 0.025 + least_reduction = 0.9 + if voltage_difference >= max_difference: + scale_factor = most_reduction + elif voltage_difference < min_difference: + scale_factor = least_reduction + else: + scale_factor = most_reduction + ((voltage_difference - min_difference) / + (max_difference - min_difference)) * (least_reduction - most_reduction) + + self.__charge_current = self.current * scale_factor + elif voltage_difference <= -0.02: + self.__charge_current = min( + self.__charge_current * 1.1, self.__config.max_charge_current) + return self.__charge_current @property def discharge_current_setpoint(self) -> float: - self.__discharging_enabled = self.battery_pack.should_discharge(self.__discharging_enabled) + self.__discharging_enabled = self.battery_pack.should_discharge( + self.__discharging_enabled) if self.__discharging_enabled is True: if self.__discharging_timer.ready: return self.__config.max_discharge_current diff --git a/test/bms/test_Bms.py b/test/bms/test_Bms.py new file mode 100644 index 0000000..bca8d76 --- /dev/null +++ b/test/bms/test_Bms.py @@ -0,0 +1,66 @@ +import unittest +from battery.battery_pack import BatteryPack # type: ignore +from bms import Config +from bms.bms import Bms +from bms.current_sensor.current_sensor import CurrentSensor + + +class FakeCurrentSensor(CurrentSensor): + def __init__(self) -> None: + self.fake_current = 10.0 + + @property + def current(self): + return self.fake_current + + +class FakePack(BatteryPack): + def __init__(self): + self.fake_high_cell_voltage = 3.9 + self.ready = True + + @property + def high_cell_voltage(self): + return self.fake_high_cell_voltage + + +class BmsTestCase(unittest.TestCase): + def setUp(self): + self.config = Config("config.default.json") + self.pack = FakePack() + self.current_sensor = FakeCurrentSensor() + self.bms = Bms(self.pack, self.config, self.current_sensor) + return super().setUp() + + def test_charge_current_setpoint_under_voltage(self): + self.assertEqual(self.bms.charge_current_setpoint, 100) + + def test_charge_current_setpoint_over_voltage(self): + self.pack.fake_high_cell_voltage = 4.35 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 5) + self.pack.fake_high_cell_voltage = 4.2 + self.current_sensor.fake_current = 5 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 3.16666667) + self.pack.fake_high_cell_voltage = 4.12 + self.current_sensor.fake_current = 3.16 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.844) + self.pack.fake_high_cell_voltage = 4.09 + self.current_sensor.fake_current = 2.844 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.844) + self.pack.fake_high_cell_voltage = 4.1 + self.current_sensor.fake_current = 2.844 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.844) + self.pack.fake_high_cell_voltage = 4.11 + self.current_sensor.fake_current = 2.844 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.5596) + self.pack.fake_high_cell_voltage = 4.1 + self.current_sensor.fake_current = 2.5596 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.5596) + self.pack.fake_high_cell_voltage = 4.04 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 2.81556) + self.pack.fake_high_cell_voltage = 4.05 + self.current_sensor.fake_current = 2.81556 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 3.097116) + self.pack.fake_high_cell_voltage = 4.09 + self.current_sensor.fake_current = 2.81556 + self.assertAlmostEqual(self.bms.charge_current_setpoint, 3.097116)